免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

中文关键词抽取三种方法:TF-IDF、TextRank与Word2Vec对比

中文关键词抽取三种方法:TF-IDF、TextRank与Word2Vec对比 简介面向需要完成课程设计或入门中文文本关键词抽取的Python开发者这是一份方法对照与实战并重的资料包。内容围绕TF-IDF、TextRank、Word2Vec词向量聚类三种主流抽取方案展开逐一梳理其原理公式、算法流程以及Python实现代码并给出关键参数与细节处理说明。同时针对实验可改进之处做了专门总结例如Word2Vec训练语料可加入领域文本、对标题词赋予初始权重、根据文档类别数调整KMeans的n_clusters值以及过滤全文档高频词等对后续优化很有启发。压缩包共31个文件大小1.78MB主体包含课程论文docx、4个可运行Python脚本、14个CSV结果与数据文件、8张项目运行截图另有README与词性标注参考等文档结构清晰、便于对照实践。已有2114人学习下载适合作为NLP课程设计、关键词抽取算法对比实验的完整参考模板。1. 为什么我把三种关键词抽取方法同时放进一个项目里在做中文文本关键词抽取时我发现单靠 TF-IDF 很容易遗漏同义表达比如一篇文档从头到尾只说“数据集”而“数据样本”出现次数很少就被排到后面。TextRank 不依赖外部语料但受窗口大小影响较大Word2Vec 聚类则能捕捉语义相近的词却需要额外的词向量训练步骤。于是我用 Python 写了三个独立脚本把 TF-IDF、TextRank 和 Word2Vec 词向量聚类放到同一个工程里对比实现每个脚本输入同一份sample_data.csv和stopWord.txt输出三组候选关键词。这套代码在普通笔记本上就能跑完适合做 NLP 入门练习也适合课程设计时对比三类算法的差异。2. TF-IDF关键词抽取从词频统计到权重筛选2.1 TF-IDF的计算逻辑与关键词抽取的对应关系TF-IDF 由两个部分构成词频 TF 和逆文档频率 IDF。TF 是词在文档中出现的频率常见做法是除以该文档中的最大词频做归一化也可以直接使用原始计数。IDF 是log(总文档数 / 包含该词的文档数)用来衡量一个词在全部文档中的区分能力。两者相乘后一个词在当前文档中出现得多同时在全部文档中出现得少它的 TF-IDF 值就高越有资格成为这篇文档的关键词。因为这个方法完全基于统计不需要训练模型所以很适合作为第一个基线。资源里的keyextract_tfidf.py就是按这个思路实现的整体流程可以拆成四步读语料、分词、过滤停用词、计算 TF-IDF 并排序。如果你正在做中文文本关键词抽取的课程设计这个脚本是最容易跑通的第一版。2.2 停用词表对结果的影响在中文文本里如果不过滤“的、了、在、是”这类词TF-IDF 的 Top N 结果基本会被高频无意义词占满。资源提供的stopWord.txt是一个通用中文停用词表包含标点、数字、单字和常见虚词。实际使用时需要根据领域调整比如做计算机类文本时可以把“方法”“问题”“进行”这三个词也加进去因为它们在这类文本中几乎每篇都出现区分度很低。我一般的过滤规则是词不能是空白字符不能在停用词表里长度要大于 1。同时排除纯数字和纯符号。下面是停用词过滤前后的效果对比文本片段过滤前过滤后在数据挖掘中我们经常使用算法进行分类中, 我们, 经常, 使用, 算法, 进行, 分类数据挖掘, 算法, 分类该方法需要训练特征模型并评估精度该, 方法, 需要, 训练, 特征, 模型, 评估, 精度方法, 训练, 特征, 模型, 评估, 精度可以看到过滤后留下的词更能代表文本主题。如果你发现结果中仍然出现“有关”“相关”“基于”这类词说明停用词表还不够干净需要按批加。2.3 核心代码实现与参数说明下面是从keyextract_tfidf.py中拆出的核心逻辑保留了可直接运行的结构import jieba import math from collections import Counter # 1. 加载停用词按行读取后去空白 stopwords set() with open(stopWord.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) # 2. 读取CSV把每行文本当成一篇文档 docs [] with open(data/sample_data.csv, r, encodingutf-8) as f: for line in f: parts line.strip().split(,) # 这里假设CSV第一列是标题第二列是正文 text parts[1] if len(parts) 1 else parts[0] docs.append(text) # 3. 分词并过滤停用词保留长度大于1的词 def cut_words(text): words jieba.lcut(text) return [w for w in words if w.strip() and w not in stopwords and len(w) 1] doc_words [cut_words(d) for d in docs] # 4. 统计文档频率DF注意用set去重 df {} for words in doc_words: for w in set(words): df[w] df.get(w, 0) 1 # 5. 逐篇计算TF-IDF并取前10个词 N len(doc_words) result {} for idx, words in enumerate(doc_words): tf Counter(words) max_tf max(tf.values()) # 用最大词频做归一化 scores {} for w, cnt in tf.items(): tf_norm cnt / max_tf idf math.log((N 1) / (df.get(w, 0) 1)) 1 # 平滑IDF scores[w] tf_norm * idf top sorted(scores.items(), keylambda x: x[1], reverseTrue)[:10] result[idx] top代码的逻辑是先把每行文本视为一篇文档然后统一分词、过滤再计算每个词的文档频率。这里有几个参数需要重点说明max_tf max(tf.values())用文档词频最大值做归一化避免长文本天然得到更高词频。如果你希望突出绝对高频词也可以直接使用原始词频但短文档的关键词会被长文档盖过。idf math.log((N1)/(df1))1加 1 平滑既避免分母为 0也避免 IDF 为负数末尾的 1 让 IDF 最小也是 1.0保证纯高频词不会被完全压到 0。top[:10]取前 10 个关键词。实际项目中我会先扫描所有 TF-IDF 值的分布如果分值衰减很快说明文档主题集中可以取 5 个如果衰减平缓再适当增加数量。最终结果会写入result/keys_TFIDF.csv每行是文档序号和对应的关键词列表。如果你的输入不是 CSV 而是单独的长文本只需要把读取部分的split(,)去掉按段落切分即可。注意如果 CSV 编码不是 UTF-8读取时会报错或乱码。我一般先打开文件判断编码或者在open里指定encodingutf-8对于 GBK 文本改成gbk即可。2.4 一个TF-IDF的改进点用 TF-IDF 做关键词抽取有个常见问题标题中的词往往比正文里的同词更有区分度。我一般会引入一个标题权重把 CSV 第一列的标题分词后给对应词的 TF 值乘以 1.5 或 2。这个改动对实验结果的影响很明显在最后一章我会再给出具体代码。如果你刚跑完keyextract_tfidf.py可以先对比一下不加权重时的输出再决定是否要加。3. TextRank把文本构造成图让权重在词之间流动3.1 从PageRank到TextRank的迁移逻辑TextRank 最早是受 Google PageRank 启发。PageRank 把网页视为节点超链接视为边通过迭代计算网页的重要性。TextRank 把候选关键词视为节点两个词在同一个共现窗口内出现就建一条边然后反复更新每个节点的得分。它不需要训练语料也不需要外部标注所以很适合在没有领域词向量时使用。资源里的keyextract_textrank.py就是基于这个思想实现的整个抽取过程拆为五步分词、停用词过滤、构建共现图、迭代计算权重、排序输出。和 TF-IDF 相比TextRank 更看重词与词之间的结构关系因此对同一篇文档即使词频不高的主题词也可能获得较高排名。3.2 共现窗口和边的权重构建图的核心参数是窗口大小window。常见做法是把窗口设为 5也就是当前词和它前后最多 5 个词之间建立共现关系。窗口太大不相关的词会被连在一起窗口太小语义关联又体现不出来。我在实验中验证过window5 对于新闻类文本效果最好对于论文摘要这种句式规范的文本window4 会更紧密。边的权重可以等于两个词在窗口内共现的次数也可以做距离衰减计算。资源里采用的是共现次数累加也就是下面代码中的graph[word][neighbor] 1。这种方式简单直接但如果两个词距离很远也可能因为窗口内多次共现而获得较高权重实际使用时可以按距离做一个衰减比如权重取1 / (位置差)。3.3 迭代公式和阻尼系数TextRank 的分数更新公式与 PageRank 基本一致引入阻尼系数d默认0.85。含义是一个节点的得分由两部分组成一部分是基础得分1-d另一部分是从邻居节点传递过来的。每轮迭代时节点把自身得分按出边权重分配给邻居。迭代次数一般设 100 次或者当两次迭代的分数差小于某个阈值时提前停止。这里需要注意句子的分词结果要过滤掉停用词否则“的”“了”也会成为图中的节点。这些虚词和很多词都相连会把分数吸走导致真正有意义的词排名下降。3.4 核心代码实现与参数验证下面是从实际脚本中整理出的 TextRank 核心代码保留了便于复现的写法import jieba from collections import defaultdict # 加载停用词表并分词 stopwords set([line.strip() for line in open(stopWord.txt, encodingutf-8)]) def cut_words(text): return [w for w in jieba.lcut(text) if w.strip() and w not in stopwords and len(w) 1] # 构建共现图 def build_graph(words, window5): graph defaultdict(lambda: defaultdict(int)) for i, w in enumerate(words): # 与后面的词建立窗口内共现 for j in range(i 1, min(i window, len(words))): graph[w][words[j]] 1 graph[words[j]][w] 1 return graph # 迭代计算TextRank得分 def textrank(graph, d0.85, max_iter100): scores {node: 1.0 for node in graph} out_sum {node: sum(graph[node].values()) for node in graph} for _ in range(max_iter): new_scores {} for node in graph: s 1 - d for nb, w in graph[node].items(): if out_sum[nb] 0: s d * w / out_sum[nb] * scores[nb] new_scores[node] s scores new_scores return scores这段代码里build_graph的window5表示共现窗口大小out_sum是每个节点的出边权重总和用于归一化防止某个高频词把邻居分数推得过高。d0.85是阻尼系数迭代次数max_iter100通常足够。如果你发现结果对随机初始化敏感说明图结构太稀疏需要增大窗口或调整停用词表。TextRank 不依赖外部语料这是它最大的优势但也带来一个短板它对所有词一视同仁不会区分“数据”和“资料”的语义关系。如果文档中这两个词交替出现它们之间无法直接建立语义边。所以在实际工程里我通常把 TextRank 的结果作为候选集再用词向量做一次去重。3.5 输出结果与参数影响脚本运行后会把每篇文档的 Top N 关键词写入result/keys_TextRank.csv格式与 TF-IDF 输出保持一致方便横向对照。判断 TextRank 效果是否合理可以看输出的词是否在原文中分布均匀如果关键词全部挤在开头或结尾说明窗口大小不合适或者停用词表过滤不到位。不同窗口大小的表现如下窗口大小输出关键词示例适用场景window3机器学习, 算法, 分类短句、微博文本window5支持向量机, 文本分类, 特征新闻、一般文章window8基于, 方法, 问题 等虚词易出现长文本、综述4. Word2Vec词向量聚类从词面匹配到语义聚合4.1 为什么关键词抽取会用Word2VecTF-IDF 和 TextRank 都是字面匹配无法处理“汽车”和“轿车”这种同义关系。Word2Vec 可以从大量语料中训练出词向量让语义相似的词在向量空间中距离更近。给定一篇文档的分词结果把每个词映射到向量空间再用聚类算法把词聚为若干类每个类中距离类中心最近的词就可以作为代表关键词。资源里keyextract_word2vec_1.py和keyextract_word2vec_2.py两个脚本就是围绕这个思路写的。区别主要在于词向量模型的训练方式和使用方式下面拆开说。4.2 词向量训练语料的选择第一个脚本是直接用sample_data.csv训练一个小的 Word2Vec 模型。这种方式好处是方便不依赖外部文件坏处是语料太少词向量质量不高很多词无法获得可靠的语义表示。第二个脚本则可以加载额外的中文语料继续训练比如把维基百科或领域文本放进来让“数据”和“资料”的向量更接近。我在实际使用时先用项目语料训练一遍再用领域语料增量训练并设置min_count2过滤低频词保证至少出现两次的词才有向量。如果你的 Python 环境里还没有安装 gensim需要先装好这个库再来跑这两个脚本。项目里没有规定死安装方式常见做法是用pip install gensim装到当前虚拟环境。4.3 聚类抽取关键词的完整流程整体流程分四步先分词再用训练好的模型把词转成向量然后用 KMeans 聚类最后从每个簇里挑出离质心最近的词。下面是一个可运行的抽取函数import numpy as np from sklearn.cluster import KMeans def extract_by_cluster(words, model, n_clusters3): vecs [] valid_words [] for w in words: if w in model.wv: vecs.append(model.wv[w]) valid_words.append(w) if len(vecs) n_clusters: return valid_words # 词太少时直接返回 vecs np.array(vecs) km KMeans(n_clustersn_clusters, random_state42).fit(vecs) keywords [] for c in range(n_clusters): idx np.where(km.labels_ c)[0] # 计算每个词到当前簇质心的欧氏距离 dist np.linalg.norm(vecs[idx] - km.cluster_centers_[c], axis1) best idx[np.argmin(dist)] keywords.append(valid_words[best]) return keywords这里n_clusters是需要抽取的关键词数量如果文档主题比较多可以适当增大random_state固定随机种子确保结果可复现。注意到一个容易被忽略的点文本中的词不会全部出现在模型词表里所以要先做一次if w in model.wv过滤。低频词和罕见词被过滤掉后聚类得到的簇更稳定。那这两个脚本的差异到底在哪我对比过第一个脚本对每篇文档独立分词后直接聚类第二个脚本先把所有文档的分词结果合并用全量词向量聚类成更粗的类再把每篇文档的词映射到这些类上。后者适合处理大量短文档前者适合单篇长文档。你在跑课程设计时可以两个都运行观察输出的keys_word2vec.csv差异。4.4 关键参数与调优建议参数推荐值说明min_count2过滤低频词减少向量噪声vector_size100~200向量维度维度过高在小语料上易过拟合window5训练时的上下文窗口n_clusters5~10关键词候选簇数通常略大于最终关键词数如果你发现聚类结果中多个关键词高度相似比如“数据”和“数据集”同时被选中可以先把这些相似词合并或者在聚类前用 Word2Vec 的most_similar函数做一次同义词去重。还有一点值得强调Word2Vec 是语义模型不是频率模型所以不会直接输出每个词的重要性聚类选出的是每个语义簇的代表词需要你对簇数量做出选择。5. 三种方法的结果对比与一个提升准确率的标题加权技巧5.1 三个结果文件的格式对比运行资源里的三个脚本后result目录下会生成三个 CSV 文件。我摘取了一篇文档的前 5 个关键词做对比方法输出关键词keys_TFIDF.csv数据挖掘, 算法, 文本分类, 特征, 模型keys_TextRank.csv文本分类, 特征, 数据, 算法, 精度keys_word2vec.csv数据, 挖掘, 分类, 模型, 特征可以看到 TF-IDF 更偏向高频实词TextRank 捕捉结构上的重要词Word2Vec 则保留语义聚合后的代表词。具体选哪个取决于场景如果做舆情监控TF-IDF 足够如果做摘要TextRank 更稳如果做同义词扩展用 Word2Vec 更合适。5.2 一个容易上手的效果提升技巧给标题词加权无论哪种方法忽略标题都是一个损失。标题中的词通常是对全文的高度概括。我习惯在分词后对标题词做加权处理。以 TF-IDF 为例在统计词频时把标题命中的词频乘以一个系数比如 2.0。对 TextRank可以在构建图之前给标题中的词初始分数增加 1.5。对 Word2Vec则可以在聚类后优先从标题词所在的簇里选代表词。下面给出一个简短的 TF-IDF 加权片段# 在计算TF时假设title_words是标题分词结果 weight_factor 2.0 for w, cnt in tf.items(): if w in title_words: tf[w] cnt * weight_factor这个加权值不要超过 3否则标题词会霸榜反而丢掉正文中的有效信息。我一般会对测试集做一个小验证分别不加权和加权比较输出关键词和人工标注的重合度再决定权重系数。你也可以把三种脚本的输出合并用投票法选出最终关键词效果通常比单一方法更稳定。本文还有配套的精品资源点击获取
返回列表