简介:北京大学研究生课程《文本挖掘》的文本聚类主题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对词频取1+log变换,避免长文档词频压倒性占优;再对向量做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类”,那就循环K=3到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和beta:alpha越小,每篇文档的主题分布越尖锐,主题越单一;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个主题”,那就循环K=3到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( tokenizer=tokenize, # 自定义分词器 token_pattern=None, # 新版sklearn需置None,避免与tokenizer冲突的警告 min_df=2, # 过滤只出现1次的词,去掉拼写噪声 max_df=0.8, # 过滤出现在80%文档以上的词,去掉无区分度词 sublinear_tf=True, # 对词频取1+log,降低词频差异的干扰 ) X = vectorizer.fit_transform(corpus) # 返回稀疏矩阵 print("原始特征维度:", X.shape) # 截断SVD降到300维,提升聚类稳定性和计算效率 svd = TruncatedSVD(n_components=300, random_state=42) X_svd = svd.fit_transform(X) # 用轮廓系数扫描K,选最合适的主题数 best_k, best_score = 3, -1 for k in range(3, 11): km = KMeans(n_clusters=k, init="k-means++", n_init=10, random_state=42) labels = km.fit_predict(X_svd) score = silhouette_score(X_svd, labels) print(f"k={k} 轮廓系数={score:.3f}") if score > best_score: best_k, best_score = k, score # 用最优K重新聚类 km = KMeans(n_clusters=best_k, init="k-means++", n_init=10, random_state=42) 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_df=2意味着只出现过一次的词不进入词表,这类词通常是拼写错误或罕见专名;max_df=0.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、K+1,找出哪些样本在相邻K值下换了簇。这些边界样本属于可归属两类的模糊文本,它们占比超过10%时,说明主题边界本来就模糊,不要强行追求完美划分。第三个习惯是人审。每个簇按文档顺序均匀抽5篇,快速浏览,回答一个简单问题:这些文章放在同一个标签下,读者会不会觉得突兀。如果突兀,无论指标多高都要回头调参。
这三个习惯的成本很低,只是在每次跑完聚类后多出几分钟,却能拦住大部分指标好看、实际不可用的糟糕方案。我早期做文本聚类,只看轮廓系数选K,结果簇中心词完全说不出主题,被项目组追问了半天。后来固定随机种子、记录每次实验参数、跑完先读簇中心词再读抽样的原始文本,这三个习惯帮我避开了无数重复劳动。文本聚类没有绝对正确的答案,验证的目标是确认“这个分组能被业务理解、能被复现出来”。希望帮到你。
本文还有配套的精品资源,点击获取