资讯动态

文本聚类全流程实战:分词、TF-IDF、K-means与避坑指南

发布时间:2026/10/20 5:06:20 来源:尧图企业网站定制
简介北京大学研究生课程《文本挖掘》的文本聚类主题PPT课件共一百三十四页聚焦第五章“文本自动聚类技术”面向需要系统学习文本分析、文本聚类的研究生、科研人员或算法工程师。课程体系涵盖文本特征提取、检索、自动分类、话题检测、过滤、关联分析、自动摘要、信息抽取、智能问答等主题这份课件则深入讲解聚类这一无监督学习环节。内容从聚类与聚类分析的基本定义讲起引入无监督学习与簇的概念给出聚类的数学描述再结合市场营销、保险、城市规划、地震研究等典型应用展开随后聚焦文档聚类的四个基本步骤即文档表示、维度降低、应用聚类算法与效果评估并详细介绍精确率、召回率、F值等评价指标以及宏平均、微平均的计算方式还讨论了误差平方和准则与优质聚类方法应具备的特性。资源为一个独立的演示文稿文件大小约一点六八兆字节排版规范、图文并茂便于课堂展示或自学研读。目前已有七百四十五人浏览学习适合文本挖掘入门及进阶者下载使用。1. 文本挖掘课的聚类讲义没有标签的文本怎么自动分组用户评论、论文摘要、投诉工单……当你手上只有一大坨没有标签的文本又想快速看出它们大致分成几类时文本聚类是唯一能直接给出答案的方向。这套讲义是某高校研究生课程《文本挖掘》第五讲的完整课件总共134页主题就是聚类。它的价值在于不是只讲K-means那道公式而是把“文本到底要怎么表示聚类算法才会靠谱”这件事说透了。适合正在做文本课题的人也适合手里堆着文本却不知道怎么下手的从业者——按这套框架把数据清洗、向量化、聚类、评估走一遍你就能得到一份可解释的文本分组结果。2. 文本聚类全流程从分词到向量化的技术链路文本聚类和普通数值聚类最大的差别在于“输入”很难定义。数值数据天生是表格文本必须先被转成向量而这一步直接决定后面所有算法的上限。讲义里经典的链路是原始文本 → 清洗与分词 → 特征表示向量化 → 降维去噪 → 聚类 → 评估与簇解释。下面按这条链路展开。2.1 文本预处理分词、去停用词与词形归一预处理不是随便跑个脚本就完事。第一件事是清洗把HTML标签、URL、表情、全半角符号、乱码全部处理掉否则词表里会混进大量噪声维度。中文和英文的处理策略完全不同英文按空格切分后还要做词干提取或词形还原中文则必须分词。常见做法是使用开源分词工具加词典配合统计模型工程里最常用的是jieba更严格的项目会换用带词性标注的框架但本质都一样把句子切成有意义的词单元。分词这一步直接决定后续聚类质量一个领域词如果被切碎就会以错误形式进入词表。比如“自然语言处理”被切成“自然/语言/处理”还能接受但“神经网络”被切错成“神经/网络/经”这个词的向量表达就废了一半。我的习惯是先用分词工具默认词典跑一遍统计高频碎片再把明显是领域词的片段加进自定义词典。去停用词也要小心通用停用词表会误删业务里有意义的词比如情感分析里“不”“没有”这类否定词一删聚类结果直接串味。下表列出常用预处理动作和容易踩的坑。处理步骤常见做法作用常见问题文本清洗正则去HTML/URL/表情统一全半角去除与主题无关的噪声符号过度清洗会把业务关键信息一并删掉中文分词词典统计模型加载领域词典得到可统计的词单元领域词切碎、未登录词丢失去停用词通用停用词表自定义词表降低高频无区分词的干扰误删“不”“没有”等业务否定词英文归一词干提取或词形还原合并同一词根的不同形态过度合并导致语义信息丢失预处理做完后文本就变成了一串词的列表。这一步没有标准答案判断标准只有一条把词表打开看一眼如果满眼是碎片和噪声聚类结果一定好不了。预处理不好后面所有算法都是白跑。2.2 文本向量化从词袋到TF-IDF相似度计算的底层词列表不能直接进聚类算法必须变成数值向量。最朴素的表示是词袋模型所有文档的词拼成一个词表每篇文档统计每个词的词频得到一个维度等于词表大小的向量。问题很直接——几十万维的向量里大部分是零而且“的”“了”“在”这类词频最高却没有区分度。TF-IDF就是对词袋的改进。TF是词在文档里出现的次数IDF是逆文档频率它衡量一个词在整个语料里有多么少见。罕见的词IDF大到处都出现的词IDF被压下去这样高频无区分词自然降权。工程实现里还会加两个细节sublinear_tf对词频取1log变换避免长文档词频压倒性占优再对向量做L2归一化去掉文档长度的影响。这两点在讲义里常常一带而过却是聚类效果的胜负手。文本聚类里相似度计算是算法的底层。TF-IDF向量最匹配的是余弦相似度它只看方向不看长度长文档和短文档只要词分布相近就被视为相似。如果直接用欧氏距离文档长度会主导距离长文档之间天然更近聚类结果会变成“按字数分堆”。好在有一个工程技巧先对每行向量做L2归一化再用欧氏距离两种度量在排序上等价——很多聚类实现内部固定用欧氏距离靠这个技巧就能让K-means跑出余弦相似度的效果。我在项目里默认这么做看似绕路其实省事。2.3 降维与去噪SVD、LSA在聚类前的角色文本向量化之后是几万维的稀疏矩阵直接扔进聚类算法会吃两个亏一是计算效率低二是高维空间里距离的区分度被噪声维度稀释。理想的主题信号可能只有几十个维度剩下几万个维度基本是干扰。所以文本聚类前通常要降维最常用的是截断SVD也就是LSA。截断SVD对TF-IDF矩阵做低秩分解把文档映射到一个100到300维的低维稠密空间。这个空间里的每个维度近似于一个“隐主题”比原始词表维度更抗噪。我在实验里通常把维度设到300保留足够语义信息的同时把K-means的运行时间压到可接受范围。要注意的是SVD是线性降维它保留全局距离结构这恰恰是聚类算法需要的。TSNE和UMAP这类非线性降维完全不同它们在低维空间里用概率建模局部邻域画出来好看但全局距离结构被压扁了。有人拿TSNE的结果去喂K-means聚类结果一团糟因为TSNE每次运行的随机性还很强。正确分工是SVD用于聚类前降维TSNE只用于最后把聚类结果画成二维图汇报。这份讲义如果提到了可视化多半也会强调这条边界别让可视化工具做特征工程的活。降维方法数学本质在文本聚类中的用法截断SVD/LSA线性矩阵分解近似低秩重构聚类前把几万维稀疏降到几百维稠密PCA方差最大化投影对稠密向量二次降维可视化TSNE非线性邻居概率建模只做二维可视化不做聚类输入UMAP非线性流形学习同上可视化为主降维不是文本聚类的必选项小语料不降维也能跑。但语料超过几千篇SVD的收益就很明显聚类更稳、速度更快、簇中心词更好解释。我一般把这步当作默认流程除非实验说明某个数据集在原始空间更优。3. 聚类算法选型K-means、层次聚类与主题模型的参数怎么设讲义到了聚类算法部分核心问题就变成“选什么算法、参数怎么设”。文本聚类的常用方案有三类K-means、层次聚类、主题模型。它们各有前提选错就是翻车的开始。3.1 K-means及其变体K值、初始化与迭代收敛K-means的思想很朴素随机选K个中心迭代地把样本分到最近中心、再更新中心位置直到收敛。文本场景里它有三个关键参数。K值是第一个玄学。它不会自己出现必须事先指定。常见的拐点法在实践中经常失效因为文本高维稀疏SSE随K增大平滑下降很难看到清晰肘点。我更常用轮廓系数扫K或者直接按业务约束定K值上下界——比如“我只想分3到8类”那就循环K3到8挑轮廓系数最高的。这在第4章会展开讲。初始化和迭代参数决定K-means能不能收敛到好结果。init用k-means是文本聚类的默认要求它让初始中心尽量彼此远离显著降低掉进坏局部解的概率。n_init意思是重复几次随机初始化、最终选代价最小的结果一般设10。max_iter默认300够用tol是收敛阈值默认1e-4也不用动。语料量大到十万级以上时MiniBatchKMeans是K-means的变体每一批只取一部分样本更新中心速度能快一个数量级。代价是结果略微不稳定所以n_init要适当调大我用20。文本主题在向量空间里通常呈现团状分布这正是K-means擅长的形状所以它是文本聚类项目里最省心、最稳定的默认选择。而且它的簇中心天然可以做解释中心向量权重最高的几个词就是簇主题。3.2 层次聚类距离度量与连接准则的选择层次聚类分自底向上的凝聚式和自顶向下的分裂式文本场景基本只用凝聚式先把每篇文档当独立簇每次合并距离最近的两个簇直到只剩一个大簇。它最大的优点是输出一棵树状图你可以从图上直观判断该分几类。参数集中在两个地方。距离度量metric文本里用余弦距离也就是1减去余弦相似度别用欧氏距离的原始形式。连接准则linkage决定簇间距离怎么算single取两个簇最近样本的距离对噪声极其敏感容易拉出长链complete取最远样本倾向于紧凑圆簇average取所有样本对距离均值最均衡ward基于方差增量在数值场景表现好但在高维稀疏文本里优势不明显。我做文本实验时一般先用average配余弦效果不对再试complete。层次聚类的代价是距离矩阵要O(n²)存储几千篇文档没问题几万篇就吃紧。实际项目里我很少直接拿它在全量数据上跑而是用一套组合流程先随机抽一两千篇跑层次聚类从树状图定出K的合理范围再用这个K去做全量K-means。先定性后定量集群规模再大也不会撑爆内存。3.3 主题模型聚类LDA的主题数与超参数主题模型LDA把每篇文档看成多个主题的混合每个主题又是词上的概率分布。这本质上是一种软聚类文档不分到某一个硬簇而是每个主题都有概率权重。讲义如果把它归进文本聚类常见角度就是“软聚类”。LDA的参数比K-means多一层。主题数K同样要指定常用困惑度和主题一致性分数辅助判断但这两个指标有时互相矛盾最后还是落到人工看主题词。超参数alpha和betaalpha越小每篇文档的主题分布越尖锐主题越单一beta越小每个主题的词分布越集中。工程经验是alpha取50/K附近、beta用默认值起步跑完看主题词再调。工程实现里gensim的LdaModel和sklearn的LatentDirichletAllocation都可用。输出是文档-主题概率矩阵这个矩阵本身可以当特征再做一次硬聚类。更直接的用法是取每篇文档概率最大的主题作为硬标签再用第5章评估指标去度量效果。LDA适合你想看到“主题-词概率分布”的场景比如做主题演化分析如果只是想快速分组K-means反而更快更稳。3.4 三种方案的对比与选型算法是否需要指定K可解释性规模上限典型场景K-means需要簇中心词可直接读百万级配MiniBatch已知大致主题数量快速分组层次聚类不需要看树状图定K树状图展示层级结构几千篇小语料探索、确定K的辅助手段LDA主题模型需要主题-词概率分布十万级想看主题概率、做主题演化补充一个不常用的选项DBSCAN密度聚类。在文本高维稀疏空间里密度阈值epsilon很难找到合适的尺度TF-IDF向量各向同性的假设也站不住所以工程上很少直接拿DBSCAN处理原始文本向量。唯一相对可行的路径是先SVD降维到低维稠密表示再跑DBSCAN找非凸簇但调参成本高、稳定性差一般项目不值得折腾。4. 文本聚类避坑从中文分词到K值选择的5条实战教训做文本聚类最折磨人的不是算法原理而是那些“看起来没问题、一跑就翻车”的细节。下面5条是我在真实数据集上反复踩过的坑按现象、原因、解决三层记录每条都有血泪基础。4.1 K值难定肘部法则失效时的替代方案现象画出SSE随K变化的曲线找不到明显拐点曲线一路平缓下滑肘部法则直接失效。原因文本向量空间高维稀疏语义边界本身是重叠的SSE曲线没有数学意义上的突变点。K-means的SSE对K单调递减拐点往往只是人眼脑补出来的卡哪个K都说得通。解决用业务约束加轮廓系数替代单一指标。先按业务需求把K限定在合理区间比如“报告只能讲3到5个主题”那就循环K3到5算轮廓系数挑最高值如果业务完全无限制先用随机抽样跑一次层次聚类从树状图读出大致的簇数量范围再回填到K-means。这套组合比盯着肘部曲线靠谱得多。4.2 分词与领域词典缺失导致主题串味现象聚类出来的簇中心词全是“的了是在”这类停用词或者一堆单字碎片簇与簇之间看不出主题差异。原因分词工具默认词典缺少领域词汇专名被切碎特征空间被碎片词占满。比如某个领域的核心术语如果被切成两个普通字它作为单字出现在每篇文档里反而变成高频噪声。解决先跑一遍分词统计高频词表人工扫出被切碎的领域词加入自定义词典再用jieba.add_word逐个添加。更省力的方案是把语料里超过3个字、出现次数大于20次的连续字串提取出来交给分词工具按新词合并。分词质量直接影响向量化的每个维度预处理阶段多花一小时聚类阶段能省一整天。4.3 稀疏矩阵处理不当直接爆内存现象几万篇文档、词表几十万维代码跑到一半内存被打满进程被杀。通常是某一行写了toarray()。原因词袋矩阵的存储量级是“文档数×词表数”每一格都是浮点数。但文档平均只用到几百个词99.9%的格子是零转成稠密矩阵后内存占用直接放大了几百倍。解决全程保持scipy.sparse.csr_matrix稀疏格式。TfidfVectorizer默认返回稀疏矩阵不要调用.toarray()。只有做完TruncatedSVD降维之后矩阵才天然变成几百维的稠密数组这时再交给K-means就很安全。KMeans本身支持稀疏输入也没必要手动转稠密。4.4 用欧氏距离聚类长文档翻车现象聚类结果里每个簇的文章字数都差不多但主题完全对不上长文章被分到同一簇。原因TF-IDF向量有长度属性文档越长非零项越多、向量模长越大。欧氏距离在这种情况下被文档长度主导长文档之间天然更近主题相关性反而排不上号。余弦相似度只比较方向、忽略模长才是文本相似度的正确度量。解决最直接的做法是让聚类算法内部使用余弦距离。但多数聚类实现默认欧氏距离所以工程技巧是提前对每行向量做L2归一化归一化之后欧氏距离和余弦相似度的排序结果等价。这个一行代码的预处理比换任何算法都有效。4.5 用TSNE降维结果直接聚类现象TSNE把文档画到二维图上类群清晰可见拿它降维后的坐标去跑K-means结果却完全对不上图。原因TSNE优化的是低维空间的局部概率重点保留邻居关系全局距离结构被压变了形。而且每次运行都带随机性跑两次结果都不同本质上不是一个稳定可靠的特征工程工具。解决规定动作是“SVD负责降维TSNE负责出图”。聚类前用TruncatedSVD降到几百维聚类完成后把簇标签交给TSNE做二维可视化只能用来画给人看不能用来算距离。这也是我每次汇报PPT前提醒自己的最后一条图好看不代表算法用了它。5. 把134页PPT转成可运行的聚类实验环境、脚本与评估课件看了不等于会做文本聚类必须自己动手跑一遍才能建立直觉。这一章用最小可运行方案把讲义里的理论落成一个可以复现的中文聚类实验。5.1 最小可运行环境的搭建环境只需要三个Python库覆盖分词、向量化、聚类和评估四个环节。pip install jieba scikit-learn pandasjieba负责中文分词是中文文本聚类的必经步骤scikit-learn提供TF-IDF、SVD、K-means和所有评估指标pandas用来读取表格类语料。Python版本建议3.9以上更高版本没有兼容问题。如果后续要跑LDA主题模型再加一个gensim即可最小方案不需要。5.2 中文文本聚类的最小脚本下面这份脚本按“分词 → 向量化 → 降维 → 选K → 聚类 → 输出簇主题词”的顺序执行可以直接套在自己的语料上。import jieba import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.cluster import KMeans from sklearn.decomposition import TruncatedSVD from sklearn.metrics import silhouette_score # 停用词表请按自己的语料维护这里只做示例 STOP_WORDS {的, 了, 是, 在, 和, 有, 就, 不} def tokenize(text): words jieba.lcut(text.strip()) return [w for w in words if len(w) 1 and w not in STOP_WORDS] # corpus: List[str]每项是一篇文档原文 corpus [...] vectorizer TfidfVectorizer( tokenizertokenize, # 自定义分词器 token_patternNone, # 新版sklearn需置None避免与tokenizer冲突的警告 min_df2, # 过滤只出现1次的词去掉拼写噪声 max_df0.8, # 过滤出现在80%文档以上的词去掉无区分度词 sublinear_tfTrue, # 对词频取1log降低词频差异的干扰 ) X vectorizer.fit_transform(corpus) # 返回稀疏矩阵 print(原始特征维度:, X.shape) # 截断SVD降到300维提升聚类稳定性和计算效率 svd TruncatedSVD(n_components300, random_state42) X_svd svd.fit_transform(X) # 用轮廓系数扫描K选最合适的主题数 best_k, best_score 3, -1 for k in range(3, 11): km KMeans(n_clustersk, initk-means, n_init10, random_state42) labels km.fit_predict(X_svd) score silhouette_score(X_svd, labels) print(fk{k} 轮廓系数{score:.3f}) if score best_score: best_k, best_score k, score # 用最优K重新聚类 km KMeans(n_clustersbest_k, initk-means, n_init10, random_state42) labels km.fit_predict(X_svd) # 打印每个簇的主题词簇中心权重最高的8个词 feature_names vectorizer.get_feature_names_out() # 老版本用 get_feature_names() centers km.cluster_centers_ for i in range(best_k): top_idx centers[i].argsort()[-8:][::-1] words [feature_names[t] for t in top_idx] print(f簇{i}主题词: { / .join(words)})整个脚本的核心逻辑是四步。第一步tokenize用jieba把中文切成词列表同时过滤单字和停用词——单字字符大多是虚词语气词留在词表里只会稀释主题信号。第二步TfidfVectorizer把文档转成TF-IDF稀疏矩阵min_df2意味着只出现过一次的词不进入词表这类词通常是拼写错误或罕见专名max_df0.8把所有文档里都出现的通用词降权配合sublinear_tf把词频差异压平让高频词不要因为数字大就主导相似度。第三步TruncatedSVD把矩阵从几万维压到300维保留主要语义结构同时去噪。第四步循环K值用轮廓系数选最优主题数再用cluster_centers_取每个簇中心的特征词做可读的主题描述。新版sklearn里自定义tokenizer时记得把token_pattern设为None否则会遇到FutureWarning虽然不影响结果但也影响观感。get_feature_names_out()只在较新的sklearn里可用老版本换成get_feature_names()。5.3 聚类质量的量化外部指标与内部指标聚类结果不能只靠肉眼看两类量化指标必须会算。外部指标需要金标准标签适合有标注的数据集做算法对比内部指标不需要标签适合真实无监督场景。指标是否需要标签取值范围使用场景NMI归一化互信息需要0到1越大越好有标注语料下对比不同算法ARI调整兰德指数需要约0为随机1为完全一致有标注语料下检验聚类与真实标签吻合度轮廓系数不需要-1到1越大越好无标注时选K、对比预处理方案CH指数不需要越高越好簇间离散度与簇内紧密度比值Davies-Bouldin不需要越低越好衡量簇间分离度适合快速横向对比from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score from sklearn.metrics import normalized_mutual_info_score, adjusted_rand_score # 内部指标不需要标签用于选K和对比方案 print(silhouette:, silhouette_score(X_svd, labels)) print(CH:, calinski_harabasz_score(X_svd, labels)) print(DBI:, davies_bouldin_score(X_svd, labels)) # 外部指标y_true为真实标签 # print(NMI:, normalized_mutual_info_score(y_true, labels)) # print(ARI:, adjusted_rand_score(y_true, labels))内部指标用于回答“这个K比那个K好吗”“加了停用词预处理有没有变好”但要注意轮廓系数偏好凸簇文本主题如果形状不规则数值会偏低不代表聚类失败。外部指标能直接告诉你算法在已知真相下的表现真实业务里标签稀缺只能作为方法论储备。最可靠的验证仍然是人从每个簇里抽几篇原文读一遍看它们是不是真的属于同一个主题。5.4 簇解释让聚类结果从“一堆编号”变成可汇报的主题聚类跑完每个样本只有一个簇编号这个编号毫无意义。可汇报的结果必须变成“簇0是AI模型类、簇1是应用场景类”这样带语义的名字。第一步看簇中心词脚本末尾已经输出每个簇权重最高的8个词这些词基本能勾勒主题轮廓。第二步从每个簇里按间距抽取3到5篇原始文本人眼过一遍确认簇内文档确实在讲同一件事。第三步给簇命名同时记录簇内文档数和Top词形成一张简洁的主题表。如果某个簇的Top词还是混杂的通常有两种原因K设小了本应分开的两类被硬塞在一起或者预处理阶段领域词被切碎主题信号被稀释。这两种情况分别对应回到4.1和4.2去处理。簇解释这一步没有算法可用但它是聚类结果能否被业务方接受的关键别省。6. 验证聚类结果的进阶技巧轮廓系数之外还要看什么内部指标数值漂亮不代表聚类结果真的有业务意义。轮廓系数高只能说明簇内紧、簇间离但文本主题本身是连续的“紧”和“离”未必对应真实主题边界。最典型的情况是轮廓系数0.62簇中心词却完全读不出主题另一个方案轮廓系数只有0.4簇中心词却清晰分明。我后来很少单独看轮廓系数而是补三个更贴近业务真相的验证习惯。第一个习惯是稳定性检验。同一个K、固定random_state不换重复跑5次K-means两两计算ARI。如果5次的标签彼此差异很大说明数据本身没有清晰的簇结构或者K不对。ARI接近1时算法结果才值得信任。第二个习惯是K敏感性分析分别跑K-1、K、K1找出哪些样本在相邻K值下换了簇。这些边界样本属于可归属两类的模糊文本它们占比超过10%时说明主题边界本来就模糊不要强行追求完美划分。第三个习惯是人审。每个簇按文档顺序均匀抽5篇快速浏览回答一个简单问题这些文章放在同一个标签下读者会不会觉得突兀。如果突兀无论指标多高都要回头调参。这三个习惯的成本很低只是在每次跑完聚类后多出几分钟却能拦住大部分指标好看、实际不可用的糟糕方案。我早期做文本聚类只看轮廓系数选K结果簇中心词完全说不出主题被项目组追问了半天。后来固定随机种子、记录每次实验参数、跑完先读簇中心词再读抽样的原始文本这三个习惯帮我避开了无数重复劳动。文本聚类没有绝对正确的答案验证的目标是确认“这个分组能被业务理解、能被复现出来”。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑