☰
Python中文文本关键词抽取:TF-IDF、TextRank与Word2Vec三种方法实战
2026/10/11 20:41:22 网站建设 项目流程

简介:这份资源面向Python初学者、NLP入门者及需要完成课程设计的学生,系统讲解中文文本关键词抽取的三种经典方法:TF-IDF、TextRank与Word2Vec词向量聚类。内容从原理、流程到代码实现逐层展开,并附课程论文、项目源码及运行截图,帮助读者理解各方法的适用场景与差异。压缩包共31个文件,约1.78MB,包含4个py脚本分别对应三种抽取方法及词向量训练,14个csv数据文件与结果文件、8张png截图、2个txt词表与停用词文件、1个docx论文、1个md说明及license,结构清晰便于对照学习。目前已有2116人学习下载。读者可获得完整可运行的代码与样例数据,直接复现关键词抽取流程,同时论文中讨论了语料扩充、标题权重、聚类数设置、高频词过滤等改进方向,为后续优化提供思路,适合作为课程设计或NLP实践的参考模板。

1. 中文关键词抽取三种路线:从一份课程设计源码包说起

做中文文本处理的人迟早会撞上同一个需求:给一篇没有标签的文章,自动挑出它到底在讲什么。搜索、推荐、舆情监控、论文查重,底层都绕不开关键词抽取。我手上这份基于Python实现中文文本关键词抽取的三种方法.zip,就是把这个需求拆成三条可跑的路线——TF-IDF、TextRank、Word2Vec 词向量聚类,配了课程论文、源码、样例数据和停用词表。它不是工业级框架,而是一套能让你把三种方法的输入输出、参数影响、失败边界都亲手摸一遍的实验台。适合正在做课程设计的学生、需要快速搭关键词抽取原型的工程师,以及想搞清楚「为什么同一篇文章三种方法给出的词不一样」的从业者。下面我按自己拆包复现的顺序,把每条路线的原理、代码、参数和坑讲清楚。

2. TF-IDF 与 TextRank:两条无监督路线的代码落地

2.1 TF-IDF 的统计逻辑与keyextract_tfidf.py拆解

TF-IDF 的核心假设很朴素:一个词在当前文档里出现得越多、在整个语料里出现得越少,它就越能代表这篇文档。词频 TF 衡量局部重要性,逆文档频率 IDF 惩罚那些到处都有的通用词。这份源码里keyextract_tfidf.py走的是「分词 → 构建词频矩阵 → 算 TF-IDF 权重 → 排序取 TopN」的流程,依赖 jieba 做中文分词,用 scikit-learn 的TfidfVectorizer或手写统计都能实现。

我一般会先确认分词和停用词这两步,因为它们直接决定后面权重的质量。源码里stopWord.txt是独立文件,data目录放原始语料,result目录存输出。下面是我按源码逻辑重写的一段可独立运行的最小实现,方便你对照理解每一步在干什么:

import jieba from sklearn.feature_extraction.text import TfidfVectorizer # 1. 读取停用词,中文停用词表通常包含"的、了、在、是"这类高频虚词 def load_stopwords(path): with open(path, 'r', encoding='utf-8') as f: return set(line.strip() for line in f if line.strip()) # 2. 自定义分词器:jieba 切词后过滤停用词和单字 def tokenizer(text, stopwords): words = jieba.lcut(text) return [w for w in words if w not in stopwords and len(w) > 1] stopwords = load_stopwords('stopWord.txt') docs = [ "中文文本关键词抽取是自然语言处理的基础任务", "TF-IDF 通过词频和逆文档频率衡量词语重要性", "TextRank 借助图模型对词语进行排序" ] # 3. 构建 TF-IDF 矩阵,tokenizer 接收单个文档返回词列表 vectorizer = TfidfVectorizer(tokenizer=lambda t: tokenizer(t, stopwords)) tfidf_matrix = vectorizer.fit_transform(docs) # 4. 取第一篇文档的 TopN 关键词 feature_names = vectorizer.get_feature_names_out() scores = tfidf_matrix[0].toarray()[0] topn = sorted(zip(feature_names, scores), key=lambda x: x[1], reverse=True)[:5] print(topn)

逻辑上分四段:停用词加载、分词过滤、矩阵构建、排序输出。参数上最需要动的是len(w) > 1这个单字过滤阈值——中文里「人」「事」这类单字有时是关键词,但多数场景过滤掉能减少噪声;TfidfVectorizer的tokenizer参数必须接收「单个文档字符串」返回词列表,如果你直接把整个语料传进去会报错。源码里keys_TFIDF.csv就是这套流程的输出,字段是词语加权重,方便你直接比对。

注意:TF-IDF 对短文本很敏感。文档只有一两句话时,IDF 几乎失效,因为语料太小、每个词都只出现在少数文档里,权重区分度会崩掉。这也是为什么源码的sample_data.csv要放多篇文档,而不是单篇。

2.2 TextRank 的图排序与keyextract_textrank.py实现

TextRank 借的是 PageRank 的思路:把词当节点,共现关系当边,谁被重要节点连得多、连得紧,谁就重要。它不依赖外部语料,单篇文档就能跑,这是它相对 TF-IDF 的最大优势。源码里keyextract_textrank.py的流程是「分词 → 滑动窗口建共现图 → 迭代计算节点权重 → 排序取 TopN」。

共现窗口大小是这里最关键的参数。窗口设 2,只有相邻词算共现;设 5,一句话里隔几个词也算关联。窗口越大,图越稠密,通用词越容易互相抬权重,结果反而变糊。我一般从 2 到 3 起步,看结果再调。下面是对应实现:

import jieba import networkx as nx def build_textrank(text, stopwords, window=3, topn=5): words = [w for w in jieba.lcut(text) if w not in stopwords and len(w) > 1] graph = nx.Graph() # 滑动窗口建边:窗口内任意两词之间加一条边 for i, word in enumerate(words): for j in range(i + 1, min(i + window, len(words))): if graph.has_edge(words[i], words[j]): graph[words[i]][words[j]]['weight'] += 1 else: graph.add_edge(words[i], words[j], weight=1) # pagerank 迭代,weight 作为边权影响传递 scores = nx.pagerank(graph, weight='weight') return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:topn] stopwords = set(open('stopWord.txt', encoding='utf-8').read().split()) text = "中文文本关键词抽取是自然语言处理的基础任务,TextRank 借助图模型对词语进行排序" print(build_textrank(text, stopwords))

window控制共现范围,nx.pagerank的weight参数让重复共现的词对权重更高。源码输出keys_TextRank.csv,你可以拿同一篇文档分别跑 TF-IDF 和 TextRank,对比两个 CSV 的重合度——重合高的词通常是真正稳定的关键词,差异大的词往往暴露了方法本身的偏好。

提示:TextRank 在长文档上表现更稳,短句上容易退化成「谁出现次数多谁赢」,因为图太小、迭代收敛快,排序信息不足。

3. Word2Vec 词向量聚类:从语义相似度到关键词簇

3.1keyextract_word2vec_1.py与_2.py的分工

这份资源里 Word2Vec 路线有两个脚本,keyextract_word2vec_1.py和keyextract_word2vec_2.py,从命名和vecs目录看,一个负责训练或加载词向量,另一个负责聚类和关键词输出。Word2Vec 的思路和前两种完全不同:它不统计词频,而是把词映射成稠密向量,语义相近的词在向量空间里距离近,然后对向量做聚类,从每个簇里挑代表词作为关键词。

这条路线的价值在于能抓到「字面不同但语义相关」的词。比如「汽车」和「轿车」在 TF-IDF 里是两个独立词,在 Word2Vec 空间里距离很近,聚类后可能归到同一簇。代价是你需要一个像样的训练语料,否则词向量质量差,聚类结果就是玄学。

3.2 词向量训练与 KMeans 聚类的参数设置

源码的vecs目录存的是训练好的向量,keyextract_word2vec_2.py里应该包含 KMeans 聚类。下面是我按这条路线复现的核心代码,用 gensim 训练词向量,再用 KMeans 对文档中的词向量聚类:

import jieba import numpy as np from gensim.models import Word2Vec from sklearn.cluster import KMeans # 1. 准备语料:每篇文档分词后作为一行 corpus = [ "中文 文本 关键词 抽取 自然语言 处理".split(), "词向量 语义 相似度 聚类 关键词".split(), "TF-IDF TextRank 图模型 统计 方法".split() ] # 2. 训练 Word2Vec,vector_size 是向量维度,window 是上下文窗口 model = Word2Vec(corpus, vector_size=100, window=5, min_count=1, workers=4) # 3. 取目标文档中的词,映射成向量 target_words = [w for w in jieba.lcut("中文文本关键词抽取与词向量聚类") if len(w) > 1] vectors = [model.wv[w] for w in target_words if w in model.wv] # 4. KMeans 聚类,n_clusters 决定聚成几类 if len(vectors) >= 2: kmeans = KMeans(n_clusters=2, n_init=10, random_state=42) labels = kmeans.fit_predict(np.array(vectors)) for word, label in zip(target_words, labels): print(label, word)

vector_size一般设 100 到 300,语料小就设小一点,否则每个词都过拟合;window是上下文窗口,中文一般 5 左右;min_count=1是因为样例语料太小,实际项目里通常设 2 到 5 过滤低频词。n_clusters是最需要根据数据调的参数——摘要里也提到,如果测试集有多个分类,n_clusters应该设成分类个数。设错了,聚类结果要么全挤一簇,要么碎成单点。

注意:Word2Vec 训练语料如果只有几篇文档,词向量基本没有语义区分度,聚类结果和随机差不多。源码里vecs目录预存了向量,说明作者也意识到现场训练不现实,直接加载更稳。

4. 避坑与排查:三种方法跑不通时先看这几处

4.1 分词和停用词没对齐,结果全是噪声

现象:跑出来的关键词里混着「的」「了」「我们」这类词,或者全是单字。原因通常是停用词表没加载成功,或者分词后没做长度过滤。stopWord.txt的编码如果是 GBK,用 UTF-8 读会乱码,过滤就失效。解决:先打印停用词集合的长度和前几个词确认加载正确,再检查len(w) > 1这类过滤条件是否生效。

4.2 TF-IDF 在单篇文档上权重全为 0

现象:keys_TFIDF.csv里所有词权重一样或者为空。原因是 IDF 计算依赖多篇文档,只有一篇时TfidfVectorizer的 IDF 退化成常数,甚至报空词汇表。解决:确保sample_data.csv里有多篇文档,或者改用「词频 + 位置权重」的简化方案。这也是为什么源码要配一个样例数据集,而不是让你随便丢一段文本进去。

4.3 TextRank 图太大导致迭代慢或内存爆

现象:长文档跑 TextRank 时卡住,或者内存占用飙升。原因是共现窗口设得太大,词与词之间边数呈平方级增长。解决:把window降到 2 到 3,或者在建图前先过滤掉低频词,只保留出现次数超过阈值的词作为节点。源码里词性标注参考.txt其实提示了一个方向——只保留名词、动词、形容词这类实词建图,能大幅减小图规模。

4.4 Word2Vec 加载预存向量时报维度不匹配

现象:keyextract_word2vec_2.py加载vecs目录下的向量时抛KeyError或维度错误。原因是训练时的vector_size和加载时不一致,或者目标词不在词表里。解决:加载后先打印model.wv.vector_size和model.wv.index_to_key[:10]确认,对不在词表的词做跳过处理,不要直接索引。

4.5 三种方法结果差异大,不知道该信哪个

现象:同一篇文档,TF-IDF 给出「文本、抽取」,TextRank 给出「关键词、方法」,Word2Vec 给出「语义、聚类」,几乎没有交集。原因不是代码错了,而是三种方法的假设不同:TF-IDF 看统计稀有性,TextRank 看共现结构,Word2Vec 看语义分布。解决:把三份 CSV 做交集和并集,交集词作为高置信关键词,并集词作为候选,再人工或规则筛一遍。这也是这份课程设计最有价值的地方——它让你亲眼看到方法差异,而不是背概念。

5. 进阶技巧:把三种结果融合成一份可用的关键词表

三种方法各跑一遍之后,真正能落地的是融合策略。我一般会做加权投票:TF-IDF 权重归一化后占 0.4,TextRank 占 0.3,Word2Vec 聚类代表词占 0.3,同一词在多路出现就累加分数,最后取 TopN。这样既保留了统计上的稀有词,也兼顾了结构重要词和语义代表词。源码里的result目录已经分好了三份 CSV,你只需要写一个合并脚本:

import pandas as pd # 三份结果各自读入,假设列名为 word, score tfidf = pd.read_csv('result/keys_TFIDF.csv').rename(columns={'score': 'tfidf'}) textrank = pd.read_csv('result/keys_TextRank.csv').rename(columns={'score': 'textrank'}) w2v = pd.read_csv('result/keys_word2vec.csv').rename(columns={'score': 'w2v'}) # 按 word 外连接合并,缺失值填 0 merged = tfidf[['word', 'tfidf']].merge(textrank[['word', 'textrank']], on='word', how='outer') merged = merged.merge(w2v[['word', 'w2v']], on='word', how='outer').fillna(0) # 各自归一化后加权求和 for col in ['tfidf', 'textrank', 'w2v']: if merged[col].max() > 0: merged[col] = merged[col] / merged[col].max() merged['final'] = merged['tfidf'] * 0.4 + merged['textrank'] * 0.3 + merged['w2v'] * 0.3 print(merged.sort_values('final', ascending=False).head(10))

参数上,三个权重不是固定的,如果你的语料领域性强、通用词多,可以提高 TF-IDF 权重;如果文档结构清晰、句子长,TextRank 权重可以调高;如果语料本身语义丰富、同义词多,Word2Vec 权重值得加大。验证方法也简单:拿几篇你人工标过关键词的文档,看融合结果和人工标注的重合率,比单跑任何一种方法都高,就说明权重方向对了。

还有一个容易被忽略的技巧来自摘要里的提示:标题文本往往包含文档的重要信息,可以对标题中出现的词给一个初始权重加成。具体做法是在分词阶段记录词是否出现在标题里,融合时给这些词乘一个 1.2 到 1.5 的系数。这个改动很小,但在新闻和论文类文档上提升明显。另外,如果某些词在所有文档里都高频出现,比如「研究」「方法」,可以在融合前直接按文档频率阈值剔除,避免它们靠 TF-IDF 的 IDF 惩罚不够而混进结果。

我从第一次跑这份源码到现在,养成了一个习惯:任何关键词抽取任务,先跑三种方法看交集,再决定用哪种融合权重,绝不单信一种。这份资源最大的价值不是代码多精妙,而是它把三条路线的输入输出都摆在你面前,让你自己踩一遍坑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询