
简介这份资源面向机器学习初学者与高校课程设计学生聚焦中文文本的无监督聚类任务提供KMeans、DBSCAN、LDA与Single_Pass四种算法的Python实现。其中KMeans与DBSCAN分别基于划分和密度完成中文文本聚类LDA从主题模型角度建模文档分布Single_Pass则采用单遍策略无需预先设定类别数适合流式或类别未知的场景。压缩包共35个文件以py脚本、sample样例、head与master等Git版本控制文件、txt数据与说明文档为主整体约139KB目录按算法模块划分结构清晰便于对照阅读。资源内附测试数据与停用词表可直接运行验证各算法效果。目前已有1622人学习下载适合希望快速理解文本聚类流程、对比不同算法特性并完成课设实践的同学参考。1. 文本聚类课设怎么选算法KMeans、DBSCAN、LDA、Single_Pass 一次跑通做课程设计最怕的不是写不出代码而是选错算法、跑不出结果、答辩时说不清为什么用这个不用那个。这份text_clustering.zip把四种中文文本聚类方案打包在一起KMeans、DBSCAN、LDA 主题聚类、Single_Pass 单遍聚类每个算法一个独立脚本配了test_data.txt、test_data2.txt两份测试语料和一份stop_words.txt停用词表。它解决的核心问题是让你在同一份中文语料上横向对比四种聚类策略的差异而不是只跑通一个就交差。适合正在做机器学习课设、需要快速搭出可演示聚类流程的本科生也适合想补一下中文文本预处理和聚类评估实操的入门者。下面按「数据怎么进、算法怎么跑、参数怎么调、坑在哪」的顺序拆开讲。2. 中文文本预处理与向量化从 test_data.txt 到聚类输入矩阵四种算法共用同一套前置流程读语料、分词、去停用词、转成数值矩阵。这一步做不对后面四个脚本全白跑。很多人拿到代码直接python KmeansClustering.py就报错八成是卡在分词库没装或者语料编码不对。2.1 语料读取与 jieba 分词中文不像英文有天然空格分隔必须先分词。这份代码用的是 jieba常见做法是jieba.lcut()逐行切词。先确认你的 Python 环境能 import jieba没有就装pip install jieba scikit-learn numpy读取语料时要注意编码。test_data.txt一般是 UTF-8但 Windows 下用记事本另存过可能变成 GBK读出来就是乱码。稳妥写法是显式指定编码读不到再回退# 读取语料每行一条文本 def load_corpus(path): with open(path, r, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] return lines # 加载停用词表去掉换行和空行 def load_stopwords(path): with open(path, r, encodingutf-8) as f: return set(w.strip() for w in f if w.strip())load_corpus里if line.strip()是为了跳过空行否则空字符串进分词会得到空列表后面构建矩阵时维度对不上。load_stopwords返回 set 而不是 list因为去停用词是高频查找操作set 的 O(1) 比 list 的 O(n) 快得多语料一大差距就出来了。2.2 去停用词与 TF-IDF 向量化分词后要过滤停用词和单字。停用词表stop_words.txt里通常是「的、了、在、是」这类高频无意义词单字也建议去掉因为中文单字歧义太大。过滤完用 TF-IDF 把文本转成向量这是 KMeans 和 DBSCAN 的输入基础。import jieba from sklearn.feature_extraction.text import TfidfVectorizer def preprocess(corpus, stopwords): docs [] for line in corpus: words jieba.lcut(line) # 去停用词 去单字 去纯数字 words [w for w in words if w not in stopwords and len(w) 1 and not w.isdigit()] docs.append( .join(words)) return docs # 向量化TF-IDF限制最大特征数防止维度爆炸 vectorizer TfidfVectorizer(max_features2000) X vectorizer.fit_transform(docs)max_features2000是个经验值。语料只有几百条时词表可能几千维但真正有区分度的词没那么多限制到 2000 维能显著降内存、提速还能缓解稀疏问题。如果你的语料主题很集中可以降到 500 到 1000 试试聚类效果未必变差。X是稀疏矩阵KMeans 能直接吃但 DBSCAN 计算距离时最好转成稠密数组或降维否则距离计算会很慢。提示test_data.txt和test_data2.txt两份语料建议分别跑一遍。同一套参数在不同语料上聚类数可能差很多答辩时能拿这个对比说明「参数要随数据调」比只跑一份有说服力。3. KMeans 与 DBSCAN 实战类别数怎么定、噪声点怎么处理这两个是划分式和密度式聚类的代表放一起讲是因为它们的核心矛盾正好相反KMeans 要你事先告诉它分几类DBSCAN 不用告诉类别数但要你调密度参数。课设里最常被问的就是「K 怎么选」和「DBSCAN 为什么一堆点没归类」。3.1 KMeans 聚类与肘部法选 KKmeansClustering.py的核心就是KMeans(n_clustersk)。难点不在调用在 k 取多少。常见做法是肘部法k 从 2 试到 10看 inertia簇内平方和随 k 下降的拐点。from sklearn.cluster import KMeans import matplotlib.pyplot as plt inertias [] K_range range(2, 11) for k in K_range: km KMeans(n_clustersk, random_state42, n_init10) km.fit(X) inertias.append(km.inertia_) plt.plot(list(K_range), inertias, markero) plt.xlabel(k); plt.ylabel(inertia) plt.savefig(elbow.png)random_state42固定随机种子保证每次跑结果一致不然答辩演示时两次结果不一样很尴尬。n_init10是让算法用 10 组不同初始质心各跑一次取最优新版 sklearn 默认值变过显式写上更稳。inertia 曲线拐点对应的 k 就是相对合理的类别数。如果曲线平滑没有明显拐点说明语料本身类别边界模糊这时候可以结合业务判断比如新闻语料按频道数定 k。3.2 DBSCAN 的 eps 与 min_samples 调参DbscanClustering.py用DBSCAN(eps..., min_samples...)。eps 是邻域半径min_samples 是成簇最少点数。这两个参数直接决定有多少点被当成噪声label -1。from sklearn.cluster import DBSCAN from sklearn.decomposition import TruncatedSVD # 高维稀疏先降维否则距离度量失效 svd TruncatedSVD(n_components50, random_state42) X_dense svd.fit_transform(X) db DBSCAN(eps0.5, min_samples3, metriceuclidean) labels db.fit_predict(X_dense) n_clusters len(set(labels)) - (1 if -1 in labels else 0) n_noise list(labels).count(-1) print(f簇数: {n_clusters}, 噪声点: {n_noise})TF-IDF 矩阵动辄上千维直接算欧氏距离会受维度灾难影响所有点距离都差不多。所以先用TruncatedSVD降到 50 维左右再聚类这是文本场景下的常见做法。eps 太小几乎所有点都是噪声太大所有点挤成一簇。实操时先看噪声比例控制在 10% 到 30% 之间比较合理太高说明 eps 偏小太低说明 eps 偏大。min_samples 一般取 3 到 5语料越小取值越小。注意DBSCAN 不需要预设类别数这是它相对 KMeans 的最大卖点但代价是参数敏感。同一份语料 eps 从 0.3 调到 0.7簇数可能从 8 个变成 2 个。答辩前一定把不同参数的结果截图存好被问到能直接展示。4. LDA 主题聚类与 Single_Pass 单遍聚类不预设类别数的两条路LDA 和 Single_Pass 都不需要事先指定类别数但思路完全不同。LDA 是概率生成模型把每篇文档看成主题的混合Single_Pass 是流式增量算法来一篇归一篇适合数据陆续到达的场景。课设里这两个往往是加分项因为比单纯调 KMeans 更能体现对聚类范式的理解。4.1 LDA 主题数与困惑度评估LatentDirichletAllocationClustering.py用的是 sklearn 的 LDA。核心参数是n_components也就是主题数。选主题数常用困惑度perplexity或主题一致性困惑度越低越好。from sklearn.decomposition import LatentDirichletAllocation best_ppl, best_n float(inf), 0 for n in range(2, 11): lda LatentDirichletAllocation( n_componentsn, random_state42, learning_methodbatch, max_iter50) lda.fit(X) ppl lda.perplexity(X) print(f主题数 {n}, 困惑度 {ppl:.2f}) if ppl best_ppl: best_ppl, best_n ppl, nlearning_methodbatch适合小语料全量迭代比在线学习更稳。max_iter50是迭代上限太小可能没收敛太大会拖时间50 对几百条语料够用。困惑度曲线一般先降后升最低点对应主题数。但困惑度不是唯一标准还要看每个主题下的高频词是否可解释——如果几个主题的高频词高度重叠说明主题数取多了得往下调。LDA 输出的是文档-主题分布矩阵要得到硬聚类结果取每篇文档概率最大的主题作为标签doc_topic lda.transform(X) labels doc_topic.argmax(axis1)4.2 Single_Pass 的相似度阈值与增量逻辑single_pass_cluster.py实现的是单遍聚类维护已有簇的中心新文档来了算它和每个簇中心的相似度超过阈值就归入最相似的簇否则新建一个簇。关键参数是相似度阈值。import numpy as np from sklearn.metrics.pairwise import cosine_similarity def single_pass(X, threshold0.5): clusters [] # 每个簇的中心向量 labels [] for i in range(X.shape[0]): vec X[i] if not clusters: clusters.append(vec) labels.append(0) continue sims [cosine_similarity(vec, c)[0][0] for c in clusters] best int(np.argmax(sims)) if sims[best] threshold: labels.append(best) # 更新簇中心新老向量取平均 clusters[best] (clusters[best] vec) / 2 else: clusters.append(vec) labels.append(len(clusters) - 1) return labels阈值 0.5 是余弦相似度的常见起点。调高到 0.7 簇会变多、更细调低到 0.3 簇会变少、更粗。簇中心用「新老平均」更新是一种简化做法好处是能随新数据缓慢漂移坏处是早期文档影响会被稀释。如果语料顺序有偏比如前一半是体育后一半是财经Single_Pass 的结果会明显受输入顺序影响这是它相比 KMeans 的固有缺陷答辩时如果被问到要能说清楚。提示Single_Pass 最大的价值场景是数据流式到达、不能回头重跑。课设里可以模拟这个场景把test_data.txt按行分批喂进去观察簇数是逐步增长还是很快稳定这个动态过程是很好的演示素材。5. 避坑与排查跑不通、结果怪、答辩被问住的常见问题代码能跑不等于结果能用。下面这几条是我实际跑这类课设代码时最常翻车的地方按「现象 → 原因 → 解决」列出来照着排查能省不少时间。现象一ModuleNotFoundError: No module named jieba。原因分词库没装或者装了但用的不是当前 Python 解释器。解决先pip install jieba如果还报错用python -c import sys; print(sys.executable)确认当前解释器路径再用该路径 -m pip install jieba装到对应环境。VS Code 里尤其容易切错解释器。现象二读语料报UnicodeDecodeError。原因文件实际编码不是 UTF-8Windows 下常见 GBK。解决把encodingutf-8改成encodinggbk试一次或者用chardet检测编码。更彻底的办法是用编辑器把文件另存为 UTF-8。现象三KMeans 每次跑出来的簇标签不一样。原因没固定random_state初始质心随机。解决KMeans(n_clustersk, random_state42, n_init10)。注意簇的编号本身没有意义第 0 簇不代表第一类比较两次结果要看簇内文档集合是否一致不能直接比标签数字。现象四DBSCAN 几乎所有点都是 -1。原因eps 太小或者没降维导致高维距离失效。解决先做 TruncatedSVD 降维再把 eps 从 0.3 开始往上调每次加 0.1观察噪声比例降到 30% 以下。如果怎么调都不行说明语料本身没有明显密度结构DBSCAN 不适合换 KMeans。现象五LDA 跑完主题词全是「的、了、是」。原因停用词没过滤干净或者向量化时没限制max_features高频虚词占了主导。解决检查stop_words.txt是否被正确加载确认预处理里w not in stopwords生效同时把max_features调小让低频但有区分度的词有机会进入。现象六Single_Pass 簇数等于文档数。原因阈值太高每篇文档和现有簇都不够像全新建簇。解决把 threshold 从 0.5 降到 0.3 甚至 0.2 试。反过来如果簇数只有 1 个说明阈值太低往上调。这个参数没有通用最优值必须结合语料试。6. 四种算法横向对比与结果验证怎么证明你的聚类是有效的跑出标签只是第一步课设要拿分得证明结果有意义。最直接的办法是把四种算法的结果放一起对比再用几个指标量化。下面这张表是我跑test_data.txt时常用的对比维度你可以照着填自己的数据。算法是否预设类别数关键参数对噪声处理适合场景KMeans是n_clusters无所有点强制归类类别数已知、语料均匀DBSCAN否eps, min_samples有标记为 -1类别形状不规则、有离群点LDA是主题数n_components无软分配需要主题解释性Single_Pass否threshold无增量归类流式数据、不能重跑量化指标用轮廓系数silhouette_score最省事它衡量簇内紧密度和簇间分离度取值 -1 到 1越接近 1 越好。注意它需要稠密矩阵稀疏的 TF-IDF 要先降维from sklearn.metrics import silhouette_score # X_dense 是前面 SVD 降维后的结果 score silhouette_score(X_dense, labels) print(f轮廓系数: {score:.3f})但轮廓系数有个坑它假设簇是凸的、密度均匀DBSCAN 这种非凸簇算出来可能偏低不代表聚类差。所以指标只能横向比同一算法不同参数跨算法比要谨慎。更靠谱的验证是人工抽检从每个簇里随机抽 3 到 5 篇文档看它们是不是真的属于同一主题。这个动作答辩时一定要做因为老师最可能问的就是「你怎么知道分对了」。我自己的习惯是每次调完参数先不看指标先抽检每个簇的前几篇文档肉眼判断主题是否一致。如果某个簇里文档主题明显混杂说明这个簇该拆如果两个簇的文档主题几乎一样说明该合并。指标是辅助肉眼抽检才是最后一道关。从那以后我每次做文本聚类都强制走一遍「抽检 指标」双验证单看任何一个都容易自欺欺人。希望这份课设资源能帮你把四种聚类算法真正跑通、讲明白而不是只交一份能运行的代码。本文还有配套的精品资源点击获取