资讯动态

影评文本聚类实战:K-Means、层次聚类与DBSCAN对比及与分类方法对照

发布时间:2026/10/17 10:58:16 来源:尧图企业网站定制
1. 影评文本聚类项目的整体设计思路影评文本分类这件事乍一看是个很典型的监督学习任务有标签、有文本、有明确的正面/负面类别直接上朴素贝叶斯或者逻辑回归就能跑出一个不错的基线。但这次我刻意绕开了监督学习的路子选择用无监督聚类来做原因其实很实在——真实场景里标注数据永远是稀缺资源。你拿到的可能只是几万条从平台上爬下来的原始评论没有任何情感标签这时候聚类就是第一把能用的刀。这个项目的核心目标有三个层次。第一层是跑通流程把影评文本从原始字符串变成聚类算法能吃的数值特征然后完成聚类。第二层是横向比较K-Means、层次聚类、DBSCAN这几种主流聚类方法在同一个影评数据集上到底谁表现更好各自的脾气是什么。第三层是纵向对照聚类这种无监督方法和朴素贝叶斯、SVM这类有监督分类方法比效果差距有多大差距体现在哪里。为什么选影评文本作为实验对象因为它有几个天然适合做聚类实验的特性。文本长度适中不像长文档那样稀疏到爆炸也不像短文本那样信息量太少情感极性相对明确正面和负面的用词差异明显聚类算法有机会捕捉到这种差异同时影评里存在大量中性、混合情感的样本这恰好能考验不同聚类方法对噪声和边界样本的处理能力。整个方案的设计逻辑是这样的先用TF-IDF把文本向量化这是文本聚类最经典也最稳妥的特征工程方案。相比词袋模型TF-IDF通过逆文档频率压制了高频无意义词的影响相比Word2Vec这类词嵌入TF-IDF不需要预训练、不需要大量语料、可解释性强对于影评这种领域相对固定的文本来说完全够用。然后分别跑K-Means、层次聚类和DBSCAN用轮廓系数、调整兰德指数等指标做定量比较。最后把聚类结果和分类结果放在同一张表里对照看看无监督方法到底能逼近有监督方法到什么程度。提示聚类做文本分类时最大的坑在于簇的语义和真实类别不是一一对应的。K-Means聚出2个簇不代表一个簇就是正面、另一个就是负面需要人工检查簇内样本来确定语义映射。2. 文本预处理与特征工程的核心细节2.1 影评文本清洗的实操要点影评文本的脏数据比想象中多。从平台上直接拿到的评论里面混着HTML标签、表情符号、URL链接、重复标点、全角半角混杂的问题。我一般按这个顺序清洗先去HTML标签用正则把.*?全部干掉然后处理URL把http开头的东西替换成空格接着统一标点把连续三个以上的感叹号、问号压缩成一个最后处理全角字符用unicodedata库做标准化。英文影评还要额外做小写化和词形还原。这里有个经验词形还原lemmatization比词干提取stemming更适合影评场景。因为影评里有很多情感形容词比如amazing和amazed词干提取会把它们都砍成amaz虽然能合并特征但丢失了词性信息词形还原会保留amazing作为形容词、amazed作为动词的区分对情感分析更友好。中文影评的处理路径不太一样。分词是第一步我习惯用jieba但要注意影评里经常出现电影名、演员名、导演名这些专有名词如果被切碎会严重影响特征质量。解决办法是提前构建一个自定义词典把数据集中高频出现的电影相关实体加进去。停用词表也不能直接用通用的影评里的电影剧情演员这些词虽然常见但它们对区分情感极性没什么帮助应该加入停用词而但是然而这类转折词在影评里往往是情感反转的信号必须保留。2.2 TF-IDF向量化的参数选择与计算过程TF-IDF的计算分两步。TF是词频一个词在单条影评里出现的次数除以该影评总词数IDF是逆文档频率等于总文档数除以包含该词的文档数再取对数。最终权重是两者相乘。参数选择上有几个关键决策点。max_features控制特征维度影评数据集我一般设在5000到10000之间。设太小会丢失信息设太大则稀疏性严重、计算变慢。min_df和max_df分别控制词的最低和最高文档频率min_df2能过滤掉只出现一次的噪声词max_df0.8能过滤掉出现在80%以上文档里的通用词。ngram_range我通常设成(1,2)也就是同时考虑单个词和相邻两个词的组合因为影评里not good这种二元组的情感含义和good完全相反只取一元词会丢失这种关键信息。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features8000, min_df2, max_df0.8, ngram_range(1, 2), sublinear_tfTrue, stop_wordscustom_stopwords ) X vectorizer.fit_transform(cleaned_reviews)sublinear_tfTrue这个参数值得单独说。默认的TF是线性计数一个词出现10次权重就是出现1次的10倍。但实际语言里词频的边际效应是递减的出现10次和出现20次的区分度远没有1次和2次的区分度大。开启sublinear_tf后TF变成1 log(tf)能有效压缩高频词的权重实测在影评聚类上轮廓系数能提升0.03到0.05。注意TF-IDF矩阵非常稀疏影评数据集通常稀疏度在99%以上。做距离计算时不要用欧氏距离稀疏向量的欧氏距离会被大量零值主导余弦相似度才是正确选择。这也是为什么K-Means在文本上要配合余弦距离使用。2.3 降维处理为什么以及怎么做TF-IDF矩阵动辄几千维直接聚类有两个问题一是维度灾难导致距离度量失效二是计算量大。降维不是可选项是必选项。我试过三种方案。PCA主成分分析是最常用的但它假设数据服从高斯分布对稀疏的文本矩阵不太友好降维后解释性也差。TruncatedSVD截断奇异值分解是文本场景的标配它不需要数据中心化能直接处理稀疏矩阵sklearn的文档里也明确推荐文本用SVD而非PCA。NMF非负矩阵分解则有一个独特优势分解出的成分都是非负的可以理解为“主题”每个主题对应一组词的权重解释性比SVD更强。实测下来影评数据集降到50到200维比较合适。降太少会丢失区分信息降太多则降维的意义不大。我一般先用SVD降到100维跑一遍看累计解释方差比如果100维能解释60%以上的方差就够用了。from sklearn.decomposition import TruncatedSVD svd TruncatedSVD(n_components100, random_state42) X_reduced svd.fit_transform(X) print(f累计解释方差比: {svd.explained_variance_ratio_.sum():.3f})3. 三种聚类方法的实现与调参实录3.1 K-Means速度最快但需要预设簇数K-Means是文本聚类的默认选择原理简单、速度快、容易并行化。它的核心逻辑是迭代地更新簇中心和样本归属直到收敛。在影评数据上K-Means跑几万条数据通常几秒就能出结果。但K-Means有两个硬伤。第一是必须预先指定簇数K而影评数据里你往往不知道真实类别有几个。解决办法是用肘部法或轮廓系数来选K。肘部法是看不同K值下的簇内平方和inertia找到下降速度突然变缓的拐点轮廓系数则直接衡量聚类质量取值在-1到1之间越接近1越好。我一般两个指标一起看肘部法给个大致范围轮廓系数做精细选择。第二是K-Means假设簇是球形的、大小相近的但影评的情感分布往往不是这样。正面评论可能高度集中大家都用类似的赞美词负面评论则五花八门吐槽的角度千奇百怪这种不均匀的簇分布会让K-Means吃亏。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score inertias, silhouettes [], [] K_range range(2, 11) for k in K_range: km KMeans(n_clustersk, initk-means, n_init10, random_state42) labels km.fit_predict(X_reduced) inertias.append(km.inertia_) silhouettes.append(silhouette_score(X_reduced, labels))initk-means这个参数很关键。默认的随机初始化可能导致K-Means陷入局部最优k-means通过让初始簇中心尽可能分散显著提升了收敛质量。n_init10表示跑10次不同初始化取最好的结果虽然慢一点但稳定性提升明显。3.2 层次聚类不用预设簇数但计算量大层次聚类Agglomerative Clustering的优势是不需要预设簇数它构建一棵聚类树你可以通过树状图在任何高度切一刀得到想要的簇数。这对探索性分析特别有用因为你可以直观看到影评数据在不同粒度下的分组情况。但层次聚类的时间复杂度是O(n²)甚至O(n³)几万条影评跑起来会非常慢。我的做法是先用SVD降维再对降维后的数据做层次聚类或者先随机采样一部分数据做层次聚类看结构再对全量数据用K-Means。链接准则linkage的选择也很讲究。ward链接最小化簇内方差适合欧氏距离average链接用簇间所有点对的平均距离对噪声更鲁棒complete链接用最远点距离倾向于产生紧凑的簇。影评数据我推荐用average或ward实测ward在降维后的数据上表现更稳定。from sklearn.cluster import AgglomerativeClustering ac AgglomerativeClustering( n_clusters2, linkageward, metriceuclidean ) labels_ac ac.fit_predict(X_reduced)3.3 DBSCAN能发现任意形状簇但参数敏感DBSCAN和前面两种方法思路完全不同。它不需要预设簇数能发现任意形状的簇还能自动识别噪声点。在影评场景里这意味着那些情感模糊、模棱两可的评论会被标记为噪声而不是被强行塞进某个簇。但DBSCAN有两个参数需要调eps邻域半径和min_samples核心点的最小邻居数。这两个参数对结果影响极大eps稍微变一点聚类结果可能天翻地覆。我的调参方法是先画k-距离图把每个点到第k个最近邻的距离排序后画出来拐点位置就是合适的eps。min_samples一般设为特征维度的两倍左右对于100维的SVD特征设20到30比较合理。DBSCAN在影评数据上的一个现实问题是高维稀疏数据里点与点之间的距离趋于均匀密度差异不明显DBSCAN很难找到有意义的密度结构。所以DBSCAN必须配合降维使用而且降维后的维度最好控制在50维以内。from sklearn.cluster import DBSCAN from sklearn.neighbors import NearestNeighbors # k-距离图辅助确定eps nn NearestNeighbors(n_neighbors25) nn.fit(X_reduced) distances, _ nn.kneighbors(X_reduced) k_distances np.sort(distances[:, -1]) db DBSCAN(eps0.5, min_samples25, metriceuclidean) labels_db db.fit_predict(X_reduced) n_noise list(labels_db).count(-1) print(f噪声点数量: {n_noise}, 占比: {n_noise/len(labels_db):.2%})3.4 三种方法的横向对比对比维度K-Means层次聚类DBSCAN是否需要预设簇数是否可切树否时间复杂度O(n·k·d·i)O(n²·d)O(n·log n)对噪声的处理敏感强行归类较敏感自动识别噪声簇形状假设球形取决于链接准则任意形状影评数据实测轮廓系数0.420.380.31影评数据实测耗时1万条3秒180秒45秒从表里能看出K-Means在影评数据上综合表现最好轮廓系数最高、速度最快。层次聚类质量略低且慢但胜在能提供多粒度的聚类结构。DBSCAN在影评上表现最差主要原因是高维稀疏数据的密度定义本身就不可靠大量评论被标为噪声。实操心得DBSCAN在文本聚类上不要抱太高期望。我试过在20维、50维、100维的SVD特征上分别跑DBSCAN维度越低效果越好但即使20维噪声比例也高达30%以上。如果非要用DBSCAN建议先用UMAP降维到10维以内再试。4. 聚类效果评估与分类方法对照4.1 无监督聚类的评估指标怎么选聚类评估比分类评估麻烦因为没有真实标签做直接对照。评估指标分两类内部指标和外部指标。内部指标不需要真实标签只看聚类结果本身的质量。轮廓系数是最常用的它衡量一个样本与同簇其他样本的相似度是否高于与最近邻簇样本的相似度。Calinski-Harabasz指数方差比准则计算簇间离散度与簇内离散度的比值越大越好。Davies-Bouldin指数则是簇内散度与簇间距离的比值越小越好。外部指标需要真实标签用来衡量聚类结果和真实类别的吻合程度。调整兰德指数ARI是最推荐的它校正了随机聚类的期望值取值在-1到1之间0表示随机水平。归一化互信息NMI衡量聚类标签和真实标签之间的信息重叠取值0到1。同质性homogeneity和完整性completeness则分别衡量一个簇是否只包含单一类别、一个类别是否只被分到一个簇。from sklearn.metrics import (silhouette_score, calinski_harabasz_score, davies_bouldin_score, adjusted_rand_score, normalized_mutual_info_score) def evaluate_clustering(X, labels, true_labelsNone): result { 轮廓系数: silhouette_score(X, labels), CH指数: calinski_harabasz_score(X, labels), DB指数: davies_bouldin_score(X, labels) } if true_labels is not None: result[ARI] adjusted_rand_score(true_labels, labels) result[NMI] normalized_mutual_info_score(true_labels, labels) return result4.2 聚类与分类方法的正面对照为了做这个对照我准备了一份带标签的影评数据集先假装没有标签跑聚类再用标签跑分类最后把两者的结果放在一起比。分类方法选了三个基线朴素贝叶斯MultinomialNB、逻辑回归LogisticRegression和线性SVMLinearSVC。这三个都是文本分类的经典方法速度快、效果好、调参简单。评估用准确率、F1值和AUC。方法类型准确率F1值备注K-MeansK2聚类0.710.69需人工映射簇语义层次聚类K2聚类0.680.66同上DBSCAN聚类0.540.51噪声点拉低指标朴素贝叶斯分类0.820.81基线最快逻辑回归分类0.860.85综合最优线性SVM分类0.850.84与LR接近这个结果很说明问题。聚类最好的K-Means准确率0.71分类最好的逻辑回归0.86差距15个百分点。这个差距就是“有标签”带来的信息红利。但反过来看聚类在完全没有标签的情况下能到0.71说明影评文本的情感信号确实足够强强到无监督方法也能捕捉到大部分。差距主要来自哪里我分析了混淆矩阵发现聚类方法在中性评论和混合情感评论上错得最多。比如“画面很美但剧情拖沓”这种评论分类器能从训练数据里学到“但”字后面的内容往往决定最终情感极性而聚类只能根据整体词分布把它归到某个簇无法做这种细粒度的逻辑判断。4.3 聚类结果的可解释性分析聚类做完不能只看指标还得看每个簇里到底是什么内容。我通常取每个簇的中心点最近的若干条评论人工阅读来判断簇的语义。K-Means聚出的两个簇簇0的高权重词是“精彩、推荐、演技、震撼、好看”簇1的高权重词是“无聊、失望、浪费、尴尬、拖沓”。语义映射很清晰簇0对应正面簇1对应负面。但仔细看簇1里混入了一些中性评论比如“一般般吧没什么特别的”这类评论情感强度低词分布介于正负之间被归到负面簇是因为它的用词更接近负面评论的“平淡”风格。层次聚类在K2时结果和K-Means接近但切到K4时能分出更细的类别强烈正面、温和正面、温和负面、强烈负面。这种多粒度结构是层次聚类的独特价值K-Means做不到。DBSCAN的结果就比较尴尬了。它聚出了3个簇加一大堆噪声点3个簇分别对应强烈正面、强烈负面和中性但噪声点占了35%里面既有情感模糊的评论也有因为特征稀疏而被密度算法忽略的正常评论。注意聚类做文本分类时簇的语义映射必须人工确认。我见过有人直接假设簇0是正面、簇1是负面结果准确率算出来只有0.3实际上簇0是负面、簇1是正面映射反了。用ARI这类对称指标可以避免这个问题但准确率、F1这些需要标签映射的指标一定要先确认语义。5. 实操中踩过的坑与排查技巧5.1 常见问题速查表问题现象可能原因排查方法解决方案聚类轮廓系数极低0.1特征维度过高或降维不足检查SVD解释方差比增加SVD维度或换用NMFK-Means每次结果差异大初始化敏感对比不同random_state增大n_init用k-meansDBSCAN全是噪声eps太小或维度太高画k-距离图增大eps降维到50维以下层次聚类内存溢出数据量太大检查数据条数采样或改用MiniBatchKMeans聚类结果与真实类别完全对不上簇语义映射错误人工检查簇内样本重新映射或换评估指标TF-IDF矩阵稀疏度99.5%停用词过滤不足检查min_df和max_df提高min_df扩充停用词5.2 三个我踩过的真实坑第一个坑是停用词表直接用了通用的。一开始我用了sklearn自带的英文停用词表结果发现“not”“very”“too”这些词被过滤掉了而它们在影评里恰恰是情感强度的关键修饰词。“good”和“not good”的情感极性完全相反把“not”去掉等于把信号扔了。后来我建了一个影评专用的停用词表只过滤“the”“a”“is”这类纯语法词保留所有情感相关词。第二个坑是K-Means的K值选择太随意。我一开始直接设K2因为影评就是正负两类嘛。但实际数据里中性评论占了将近20%强行分2类会把中性评论硬塞进正负簇拉低整体质量。后来用轮廓系数扫了一遍K2到10发现K3时轮廓系数最高第三类正好对应中性评论。所以不要假设簇数等于类别数让数据说话。第三个坑是DBSCAN的eps用了默认值。sklearn的DBSCAN默认eps0.5在标准化后的数据上可能勉强能用但我的SVD特征没有做标准化尺度差异大eps0.5直接把所有点都判成噪声。后来我先用StandardScaler把SVD特征标准化再画k-距离图确定eps效果才正常。DBSCAN对特征尺度极其敏感标准化是必须的前置步骤。5.3 提升聚类效果的两个进阶技巧第一个技巧是用UMAP替代SVD做降维。UMAP是一种非线性降维方法相比SVD的线性投影它能更好地保留数据的局部结构。在影评数据上UMAP降到20维后再跑K-Means轮廓系数能从0.42提升到0.51ARI从0.45提升到0.58。代价是UMAP训练比SVD慢而且参数n_neighbors、min_dist需要调。我的经验是n_neighbors设在15到50之间min_dist设在0.0到0.1之间对影评数据比较合适。第二个技巧是用词嵌入替代TF-IDF。TF-IDF的缺点是它只看词的出现不理解词义。“精彩”和“出色”在TF-IDF里是两个完全独立的特征但在语义上它们高度相似。用预训练的词向量比如在影评语料上训练Word2Vec或FastText把每条评论的所有词向量取平均得到文档向量再聚类能捕捉到这种语义相似性。实测在影评数据上Word2Vec平均向量 K-Means的ARI能到0.62比TF-IDF高出不少。但词嵌入需要大量语料训练如果数据量小还是TF-IDF更稳。# UMAP降维 K-Means的进阶方案 import umap from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X_reduced) reducer umap.UMAP(n_neighbors30, min_dist0.05, n_components20, random_state42) X_umap reducer.fit_transform(X_scaled) km_final KMeans(n_clusters3, initk-means, n_init20, random_state42) labels_final km_final.fit_predict(X_umap)实操心得UMAP的random_state一定要固定否则每次降维结果不同聚类结果也跟着变。另外UMAP降维后的坐标没有物理意义不要试图解释UMAP的每一维代表什么它只是为聚类提供了一个更好的几何空间。6. 聚类与分类的选型建议回到最初的问题影评文本分类到底该用聚类还是分类我的答案是看场景。如果你有至少几千条标注数据直接上逻辑回归或线性SVM准确率能到0.85以上调参简单、训练快、可解释性也不差。分类方法在影评任务上的优势是碾压性的没必要为了“无监督”而用聚类。如果你完全没有标注数据或者标注成本极高聚类是唯一的选择。K-Means配合TF-IDF和SVD能在几秒内给你一个0.7左右准确率的基线。这个基线虽然不如分类但足以支撑初步的数据探索和标注策略制定。你可以先用聚类把数据分成几个簇每个簇采样几十条人工标注再用这些标注数据训练分类器这就是主动学习的思路。如果你需要发现数据中的未知结构比如想知道影评里除了正负之外还有哪些隐藏的类别比如“剧情向”“演技向”“特效向”层次聚类和DBSCAN更适合探索。它们不预设簇数能让你看到数据在不同粒度下的组织方式。最后分享一个我在实际项目里的做法聚类和分类不是二选一而是流水线的两个阶段。先用聚类做数据探索和粗标注再用分类做精细预测。聚类负责发现结构分类负责利用标签。两者配合比单用任何一种都更有效。

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

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

免费获取报价 →
↑