ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

中文文本关键词抽取:TF-IDF、TextRank、Word2Vec词聚三条路线实战

中文文本关键词抽取:TF-IDF、TextRank、Word2Vec词聚三条路线实战 简介这份资源面向具备一定Python基础、希望系统掌握中文文本关键词抽取的开发者与学习者围绕TF-IDF、TextRank、Word2Vec词聚类三种主流方法展开实践。相比网上零散示例它更侧重把Word2Vec词聚类的完整流程讲清楚并以专利文本为样例同样适用于新闻、论文等中文语料。压缩包共20个文件约879KB包含14个csv结果与样例数据、4个py实现脚本、2个txt停用词与词性标注参考覆盖从数据准备到结果输出的完整链路。资源中提供了三种算法的独立实现脚本以及对应的关键词抽取结果文件便于读者横向对比不同方法的效果差异并在此基础上做融合改进或迁移到自己的语料上。目前已有443人学习下载适合想通过动手实践理解关键词抽取原理、快速搭建可复用代码框架的读者参考。1. 中文文本关键词抽取三条技术路线怎么选、怎么落地中文文本关键词抽取这件事很多人第一次做是在课程项目里给一段新闻或论文摘要要求自动吐出 5 到 10 个关键词。听起来简单真动手就会发现坑不少——中文没有天然空格分词TF-IDF 的 IDF 要靠语料统计TextRank 的图权重受窗口大小影响极大Word2Vec 词聚又涉及向量维度和聚类簇数怎么定。标题里提到的 TF-IDF、TextRank、Word2Vec 词聚恰好代表了三条不同思路统计驱动、图结构驱动、语义向量驱动。这篇文章面向的是想用 Python 把这三条路线都跑通、并且知道各自边界在哪的从业者或学生。不管你是做人工智能大作业还是想给搜索、推荐、舆情系统加一个关键词模块下面这套流程都能直接复现。我会把每条路线的原理、代码、参数、翻车点都摊开讲最后给一个组合使用的进阶技巧。2. TF-IDF 关键词抽取从分词到权重排序的完整链路2.1 为什么 TF-IDF 仍然是中文关键词抽取的基线TF-IDF 的核心思想很朴素一个词在当前文档里出现越多TF 高同时在所有文档里出现越少IDF 高它就越可能是关键词。这个思路对中文同样成立但前提是先把中文切成词。常见做法是用 jieba 分词再配合停用词表过滤掉“的、了、是”这类无意义高频词。TF-IDF 最大的优势是可解释、计算快、不需要训练适合作为第一条基线。它的短板也很明显无法捕捉词与词之间的语义关系比如“人工智能”和“AI”在 TF-IDF 眼里是两个完全无关的词。另外 IDF 依赖语料库如果你只有单篇文档IDF 就退化成常数排序基本只看 TF效果会打折扣。所以实际项目里我一般会准备一个中等规模的背景语料几百到几千篇同领域文档来算 IDF而不是只拿目标文档自己算。2.2 用 jieba sklearn 跑通 TF-IDF 最小示例下面这段代码是 TF-IDF 关键词抽取的最小可运行版本。它做了四件事分词、去停用词、构建 TF-IDF 矩阵、按权重排序取 TopN。import jieba import jieba.analyse from sklearn.feature_extraction.text import TfidfVectorizer import numpy as np # 假设我们有一个小语料库每篇文档是一个字符串 corpus [ 人工智能正在改变医疗影像诊断的方式深度学习模型可以辅助医生识别病灶, 自然语言处理技术让机器能够理解人类语言文本分类和关键词抽取是常见任务, Python 是数据科学和人工智能领域最常用的编程语言之一拥有丰富的库, 聚类算法可以将相似的文本自动分组Word2Vec 可以把词映射到向量空间, TF-IDF 和 TextRank 都是经典的关键词抽取方法各有适用场景 ] # 停用词表实际项目建议加载外部文件 stopwords set([的, 了, 是, 在, 和, 可以, 能够, 之一, 常见, 任务]) # 自定义分词函数jieba 分词后去停用词 def tokenize(text): words jieba.lcut(text) return [w for w in words if w not in stopwords and len(w) 1] # 构建 TF-IDF 向量器 vectorizer TfidfVectorizer( tokenizertokenize, token_patternNone, # 因为用了自定义 tokenizer必须设为 None max_features5000, # 控制词表大小防止稀疏矩阵过大 smooth_idfTrue # 平滑 IDF避免除零 ) tfidf_matrix vectorizer.fit_transform(corpus) feature_names vectorizer.get_feature_names_out() # 取第一篇文档的 TF-IDF 权重并排序 doc_idx 0 doc_vector tfidf_matrix[doc_idx].toarray().flatten() top_indices np.argsort(doc_vector)[::-1][:8] print(文档关键词TF-IDF) for i in top_indices: if doc_vector[i] 0: print(f {feature_names[i]}: {doc_vector[i]:.4f})逻辑说明TfidfVectorizer默认按空格分词对中文必须传入自定义tokenizer同时把token_pattern设为None否则会报错或切分异常。max_features控制词表规模语料大时建议设 10000 到 50000。smooth_idfTrue是默认值作用是给 IDF 加一平滑防止某个词在所有文档都出现导致除零。排序时用argsort取降序再过滤掉权重为 0 的词。参数方面max_df和min_df很关键。max_df0.8表示忽略出现在 80% 以上文档里的词相当于自动过滤部分停用词min_df2表示忽略只出现 1 次的词能有效降噪。这两个参数在语料较小时要谨慎调设太狠会把真正关键词也滤掉。2.3 TF-IDF 参数怎么调max_df、min_df 与 ngram_range很多人跑完上面代码发现关键词里混进了“人工智能”“模型”这种领域通用词这不是 bug是 IDF 语料不够垂直导致的。解决办法有两个一是换更垂直的背景语料二是调max_df。我一般会先跑一版max_df1.0, min_df1看原始效果再逐步收紧。ngram_range也值得试设成(1,2)可以抽出“深度学习”“文本分类”这类二元词组但词表会膨胀需要配合max_features限制。提示如果只有单篇文档不要用 sklearn 的 TfidfVectorizer 硬算直接用 jieba.analyse.extract_tags 更省事它内置了 IDF 语料适合快速验证。3. TextRank 关键词抽取把词当节点用图排序找中心词3.1 TextRank 的图结构原理与窗口参数TextRank 借用了 PageRank 的思想把每个词当作图中的一个节点如果两个词在同一个滑动窗口内共现就在它们之间连一条边。窗口大小通常设为 2 到 5表示考虑前后 N 个词。窗口越大图的连通性越强但也会引入更多弱相关边导致排序被稀释。TextRank 的优势是不依赖外部语料单篇文档就能跑而且能捕捉一定的词间共现关系。它的短板是对分词质量敏感如果分词把“人工”和“智能”切开那“人工智能”这个关键词就永远出不来。另外 TextRank 倾向于选出高频共现的词对低频但重要的专业术语可能不友好。3.2 用 jieba.analyse.textrank 抽取关键词jieba 自带 TextRank 实现调用非常简单但默认参数不一定适合你的场景。import jieba.analyse text 人工智能正在改变医疗影像诊断的方式深度学习模型可以辅助医生识别病灶。 自然语言处理技术让机器能够理解人类语言文本分类和关键词抽取是常见任务。 Python 是数据科学和人工智能领域最常用的编程语言之一拥有丰富的库。 聚类算法可以将相似的文本自动分组Word2Vec 可以把词映射到向量空间。 TF-IDF 和 TextRank 都是经典的关键词抽取方法各有适用场景。 # 使用 jieba 的 TextRank 抽取关键词 keywords jieba.analyse.textrank( text, topK10, # 返回前 10 个关键词 withWeightTrue, # 返回权重 allowPOS(ns, n, vn, v) # 只保留地名、名词、动名词、动词 ) print(TextRank 关键词) for word, weight in keywords: print(f {word}: {weight:.4f})逻辑说明allowPOS是 TextRank 最重要的过滤参数。默认只保留名词和动词类能有效过滤掉“的”“了”这类虚词。如果你做的是专业领域抽取建议把allowPOS收窄到(n, vn)只保留名词和动名词减少动词干扰。topK控制返回数量withWeightTrue可以看到权重分布方便判断阈值。参数方面jieba 的 TextRank 没有直接暴露窗口大小它内部用的是固定窗口。如果你需要更精细的控制可以用jieba.analyse.TextRank类自己实例化但更推荐用networkx手动建图这样窗口、阻尼系数、迭代次数都能调。3.3 手动建图用 networkx 控制 TextRank 的每个参数当默认实现不够用时手动建图是更可靠的做法。下面这段代码展示了如何用 networkx 构建词共现图并运行 PageRank。import jieba import networkx as nx from collections import defaultdict text 人工智能正在改变医疗影像诊断的方式深度学习模型可以辅助医生识别病灶。自然语言处理技术让机器能够理解人类语言。 # 分词并过滤 words [w for w in jieba.lcut(text) if len(w) 1 and w not in set([正在, 可以, 能够, 方式])] # 构建共现图窗口大小为 3 window_size 3 graph nx.Graph() for i, word in enumerate(words): for j in range(i 1, min(i window_size, 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], weight1) # 运行 PageRankweight 参数指定边权重 scores nx.pagerank(graph, alpha0.85, weightweight) # 排序输出 sorted_words sorted(scores.items(), keylambda x: x[1], reverseTrue) print(手动 TextRank 关键词) for word, score in sorted_words[:10]: print(f {word}: {score:.4f})逻辑说明window_size3表示每个词只和后面两个词连边这是 TextRank 的常见设置。alpha0.85是阻尼系数表示有 85% 的概率沿着边跳转15% 的概率随机跳。weightweight让 PageRank 考虑边的共现次数共现越多权重越高。手动建图的好处是你可以随时打印图结构检查是否有孤立节点或异常边。注意networkx 的 pagerank 在 2.x 和 3.x 版本间参数名有变化如果报错提示weight不支持先确认版本必要时降级或改用nx.pagerank(graph, weightweight)的兼容写法。4. Word2Vec 词聚把词映射到向量空间再做聚类4.1 Word2Vec 词聚和 TF-IDF、TextRank 的本质区别TF-IDF 和 TextRank 都是在词符号层面做文章Word2Vec 词聚则先训练词向量把每个词变成一个稠密向量再对向量做聚类。这样做的最大好处是能捕捉语义相似性“人工智能”和“机器学习”在向量空间里距离很近即使它们从不在同一句话里共现也可能被聚到同一簇。词聚的典型流程是训练 Word2Vec 模型 → 提取词向量 → 用 KMeans 或 DBSCAN 聚类 → 从每个簇里选代表词作为关键词。难点在于聚类簇数怎么定、代表词怎么选。簇数太少关键词会集中在少数几个主题簇数太多每个簇只有一个词失去聚类的意义。4.2 训练 Word2Vec 并对词向量做 KMeans 聚类下面这段代码用 gensim 训练 Word2Vec然后用 KMeans 对词向量聚类最后从每个簇里选距离簇中心最近的词作为代表。from gensim.models import Word2Vec from sklearn.cluster import KMeans import jieba import numpy as np # 准备语料实际项目建议用几千篇以上文档 corpus [ 人工智能 深度学习 医疗 影像 诊断 模型 辅助 医生 识别 病灶, 自然语言 处理 技术 机器 理解 人类 语言 文本 分类 关键词 抽取, Python 数据科学 人工智能 编程 语言 库 丰富, 聚类 算法 相似 文本 自动 分组 Word2Vec 词 映射 向量 空间, TF-IDF TextRank 经典 关键词 抽取 方法 适用 场景 ] # 分词这里语料已经用空格分好实际项目用 jieba tokenized [doc.split() for doc in corpus] # 训练 Word2Vec model Word2Vec( sentencestokenized, vector_size100, # 向量维度常用 100 到 300 window5, # 上下文窗口 min_count1, # 语料小时设为 1大语料建议 5 以上 workers4, # 并行线程数 epochs50 # 训练轮数小语料需要多跑几轮 ) # 提取词向量 words list(model.wv.index_to_key) vectors np.array([model.wv[w] for w in words]) # KMeans 聚类簇数设为 3 n_clusters 3 kmeans KMeans(n_clustersn_clusters, random_state42, n_init10) labels kmeans.fit_predict(vectors) # 从每个簇里选距离中心最近的词作为代表 print(Word2Vec 词聚结果) for cluster_id in range(n_clusters): cluster_words [words[i] for i in range(len(words)) if labels[i] cluster_id] if not cluster_words: continue # 计算每个词到簇中心的距离 center kmeans.cluster_centers_[cluster_id] distances [(w, np.linalg.norm(model.wv[w] - center)) for w in cluster_words] distances.sort(keylambda x: x[1]) representatives [w for w, _ in distances[:5]] print(f 簇 {cluster_id}: {representatives})逻辑说明vector_size100是词向量维度语料越大可以设越高但小语料设太高容易过拟合。min_count1是因为示例语料太小实际项目里设 5 能过滤低频噪声词。n_init10让 KMeans 跑 10 次不同初始化取最优避免陷入局部最优。代表词的选择逻辑是先取簇内所有词再算每个词到簇中心的欧氏距离取最近的几个。参数方面n_clusters是最难定的。我一般会先跑n_clusters从 2 到 10看轮廓系数或肘部法则再结合业务需求定。如果关键词要覆盖多个主题簇数可以多一点如果只要几个核心词簇数少一点。4.3 词聚结果怎么评估轮廓系数与人工抽检聚类没有绝对正确的答案但可以用轮廓系数做参考。轮廓系数越接近 1说明簇内越紧凑、簇间越分离。下面这段代码计算轮廓系数。from sklearn.metrics import silhouette_score score silhouette_score(vectors, labels) print(f轮廓系数: {score:.4f})逻辑说明silhouette_score接收原始向量和聚类标签返回平均轮廓系数。注意它计算量较大词表上万时可能很慢可以采样计算。轮廓系数低于 0.2 通常说明聚类结构不明显这时候要么增加语料要么减少簇数要么换用 DBSCAN 这类不需要预设簇数的算法。提示Word2Vec 词聚的关键词往往比较“泛”因为聚类倾向于把语义相近的词放一起代表词可能是“模型”“技术”这类通用词。如果要做精准抽取建议把词聚结果和 TF-IDF 权重做加权取交集或并集。5. 三条路线避坑与排查那些让我返工的血泪经验5.1 分词粒度不一致导致关键词对不上现象TF-IDF 抽出了“人工”“智能”TextRank 抽出了“人工智能”两条路线结果无法合并。原因jieba 默认分词对“人工智能”这类新词可能切错不同模块用了不同分词配置。解决统一分词函数加载自定义词典。把领域术语加到jieba.load_userdict(dict.txt)里每行格式为“词语 词频 词性”。所有路线共用同一个分词结果避免各切各的。5.2 IDF 语料不垂直导致通用词霸榜现象关键词里全是“研究”“方法”“结果”这类词。原因背景语料太杂IDF 没起到区分作用。解决换用同领域语料算 IDF或者手动维护停用词表把领域通用词加进去。我一般会先跑一版把 Top50 里明显不该出现的词记下来追加到停用词表再跑第二版。5.3 TextRank 窗口设太大导致关键词发散现象关键词数量多但都不太相关权重分布很平。原因窗口设成 10 以上图变得太稠密PageRank 分数被平均化。解决窗口回到 2 到 5同时收窄allowPOS。如果还是发散检查是否有孤立节点孤立节点会拉低整体区分度。5.4 Word2Vec 语料太小导致向量无意义现象词聚结果随机每次跑都不一样。原因语料只有几篇文档Word2Vec 学不到稳定语义。解决语料至少几千篇或者用预训练词向量。如果只能用小语料把vector_size降到 50epochs加到 100min_count1但效果仍然有限。这种情况建议放弃词聚直接用 TF-IDF 或 TextRank。5.5 聚类簇数拍脑袋导致代表词重复现象不同簇选出的代表词有重叠或者某个簇全是停用词。原因n_clusters设得不对或者向量里混入了噪声词。解决先过滤掉低频词和停用词再聚类用轮廓系数辅助选簇数。如果代表词重复说明簇间边界模糊可以尝试 DBSCAN 或层次聚类。6. 组合策略与进阶技巧让三条路线互相补位单独用任何一条路线都有明显短板实际项目里我一般会做组合。最常见的做法是TF-IDF 负责召回高频重要词TextRank 负责补充共现关系强的词Word2Vec 词聚负责发现语义相关但字面不同的词。三者取并集后再用一个简单的加权公式排序。权重可以设为 TF-IDF 0.5、TextRank 0.3、Word2Vec 0.2具体比例根据业务调。验证方法上我习惯用人工标注的小测试集找 20 篇文档每篇人工标 5 个关键词然后算抽取结果的命中率。命中率低于 60% 就回去调参数高于 80% 基本可用。这个测试集不需要大但一定要覆盖不同主题否则调出来的参数只对某一类文档有效。还有一个技巧是动态停用词先跑一遍 TF-IDF把每篇文档都出现的高频词自动加入停用词表再跑第二遍。这样能自动过滤掉领域通用词比手动维护省事。代码上就是在TfidfVectorizer的max_df基础上再叠加一个基于文档频率的过滤。# 动态停用词示例先算文档频率再过滤 from sklearn.feature_extraction.text import CountVectorizer cv CountVectorizer(tokenizertokenize, token_patternNone) cv_matrix cv.fit_transform(corpus) doc_freq np.array((cv_matrix 0).sum(axis0)).flatten() feature_names_cv cv.get_feature_names_out() # 出现在 80% 以上文档的词视为通用词 threshold 0.8 * len(corpus) dynamic_stopwords set(feature_names_cv[doc_freq threshold]) print(f动态停用词: {dynamic_stopwords})逻辑说明CountVectorizer统计每篇文档是否出现某个词doc_freq是文档频率。阈值设为 80% 文档都出现的词这些词区分度低加入停用词表。这个方法比手动维护更适应语料变化但要注意阈值别设太低否则会把真正关键词也滤掉。最后说个我自己的习惯每次跑完关键词抽取不要只看 Top10把 Top50 都打印出来扫一眼。很多问题——分词错误、停用词遗漏、权重异常——在 Top10 里看不出来Top50 里一目了然。这个习惯帮我省了很多返工时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表