简介本资源是一套面向Python初学者与数据挖掘入门者的文本分析实践项目聚焦使用sklearn库完成端到端的中文文本挖掘任务适用于课程设计、自学实训及小型NLP项目快速上手。压缩包共127个文件以20个原始txt文本样本为核心数据集辅以2个Markdown说明文档含流程梳理与关键代码注解以及大量.py脚本如预处理、TF-IDF向量化、朴素贝叶斯分类、LDA主题建模等模块化实现另有license文件与若干中间结果文件整体仅510KB轻量易解压、即取即用。已有761人学习下载反映出较强的教学适配性与实操参考价值。读者可直接复现从分词结合jieba、停用词过滤、特征提取CountVectorizer/TfidfVectorizer到监督/无监督建模Naive Bayes、LDA的完整链路并获得结构清晰的代码组织方式与可验证的分析结果输出是理解文本挖掘工程化落地的优质入门范例。1. 为什么处理100份文本文件时用sklearn做文本挖掘反而比“大模型API调用”更稳、更快、更可控你手头有100份本地文本文件比如客服工单、产品反馈、实验日志、会议纪要不是网页爬虫来的脏数据也不是实时流就是规整的.txt或.csv文件每份几百字到几千字不等。你想快速知道哪些词反复出现哪几类主题集中不同文件之间相似度如何有没有异常离群的样本——这时候别急着调用大模型API。我带某高校实验室做过3个类似规模的文本分析项目结论很实在对100份以内、结构清晰、目标明确的批量文本sklearn 原生Python是落地最快、结果最可解释、复现成本最低的路径。它不依赖网络、不产生token费用、不黑盒输出、所有向量和聚类过程都可打印、可调试、可回溯。尤其当你需要把分析逻辑嵌入内部系统、或交付给非算法背景的同事复用时一个不到200行、无外部服务依赖、输入是文件夹路径、输出是Excel报告的脚本远比“调一次API等三秒、出错查日志像解谜”来得可靠。本文就带你从零跑通这个闭环读100个文件 → 清洗 → 向量化 → 降维 → 聚类 → 可视化 → 输出带标签的原始文件清单。每一步命令可复制每个参数有依据每个坑我都替你踩过。2. 用sklearn构建端到端文本分析流水线从文件读取到TF-IDF向量化2.1 批量读取100份文本文件统一编码与容错处理实际项目中100份文件往往来自不同人、不同时间、不同编辑器.txt文件可能混用utf-8、gbk、latin-1编码有的文件末尾多空行有的含不可见控制字符。硬写open(file, r).read()必翻车。我们用pathlibchardet组合做健壮读取from pathlib import Path import chardet import re def read_text_safely(file_path: Path) - str: 安全读取单个文本文件自动检测编码清理首尾空白和常见控制符 try: # 先用chardet探测编码只读前10KB避免大文件卡顿 with open(file_path, rb) as f: raw f.read(10000) encoding chardet.detect(raw)[encoding] or utf-8 # 尝试用探测到的编码读取失败则fallback到latin-1不会报错 with open(file_path, r, encodingencoding) as f: text f.read() except (UnicodeDecodeError, OSError): with open(file_path, r, encodinglatin-1) as f: text f.read() # 清理去首尾空白、合并连续空白符、移除零宽空格等隐形字符 text text.strip() text re.sub(r\s, , text) # 合并空格、制表、换行 text re.sub(r[\u200b\u200c\u200d\uFEFF], , text) # 移除零宽字符 return text # 批量读取所有.txt文件支持子目录 root_dir Path(data/raw_texts) # 替换为你自己的文件夹路径 all_files list(root_dir.rglob(*.txt)) # rglob递归查找 print(f共找到 {len(all_files)} 个 .txt 文件) # 读取全部内容保留文件名映射 file_texts {} for file_path in all_files[:100]: # 严格限制为100份 try: content read_text_safely(file_path) # 用相对路径作为ID避免绝对路径泄露信息 rel_id file_path.relative_to(root_dir).as_posix() file_texts[rel_id] content except Exception as e: print(f跳过文件 {file_path.name}{e}) continue print(f成功加载 {len(file_texts)} 份文本)逻辑说明chardet.detect()只读前10KB而非全文件大幅提速latin-1是万能fallback编码它把每个字节当一个字符映射不会报错虽可能显示乱码但不影响后续分词因后续只关注中文/英文词频正则清理隐形字符是处理从Word、PDF复制粘贴文本的必备步骤否则TF-IDF向量里会混入无法识别的符号维度。2.2 文本清洗与标准化为什么停用词表不能直接用jieba默认的100份文件若含大量中文必须分词。但jieba默认停用词表如“的”“了”“在”在专业场景下太粗放实验日志里“结果”“显示”“未检出”是高频但无区分度的词应加入停用客服工单里“您好”“请问”“感谢”是模板话术不反映问题本质某些领域专有名词如“PCR”“ROI”“SOP”需保留不能被jieba误切或过滤。因此我们构建三层清洗策略预处理标点/数字、领域停用、自定义保留import jieba import re # 自定义停用词根据你的100份文件内容动态补充 DOMAIN_STOPWORDS { 您好, 谢谢, 请问, 可以, 一下, 这个, 那个, 已经, 正在, 进行, 完成, 结束, 开始, 结果, 显示 } # 领域关键词白名单确保不被过滤 DOMAIN_KEEPWORDS {PCR, ELISA, ROI, SOP, FDA, CE} def clean_and_tokenize(text: str) - str: 中文文本清洗分词先规则清洗再jieba分词最后过滤 # Step 1: 规则清洗 —— 移除URL、邮箱、纯数字串、多余标点 text re.sub(rhttps?://\S|www\.\S|[\w.-][\w.-]\.\w, , text) # URL/邮箱 text re.sub(r\d{4,}, , text) # 移除4位以上连续数字年份保留长编号移除 text re.sub(r[^\w\u4e00-\u9fff\s], , text) # 只留中文、英文、数字、空格 # Step 2: jieba分词 words jieba.lcut(text) # Step 3: 过滤 —— 去停用、去单字、去空白、长度2且非白名单 filtered [] for w in words: w w.strip() if not w or len(w) 2: continue if w in DOMAIN_STOPWORDS: continue if w in DOMAIN_KEEPWORDS: filtered.append(w) continue # 保留长度2的词或数字字母组合如PCR-123 if re.match(r^[a-zA-Z0-9\u4e00-\u9fff]{2,}$, w): filtered.append(w) return .join(filtered) # 应用清洗到全部文本 cleaned_docs [] doc_ids [] for doc_id, text in file_texts.items(): cleaned clean_and_tokenize(text) if cleaned.strip(): # 确保清洗后非空 cleaned_docs.append(cleaned) doc_ids.append(doc_id) print(f清洗后有效文档数{len(cleaned_docs)}原{len(file_texts)}份)参数说明re.sub(r\d{4,}, , text)是关键——它放过“2023”“123”这种短数字但干掉“123456789”这种无意义长编号DOMAIN_KEEPWORDS必须根据你100份文件的实际高频专有名词手动补全这是影响聚类质量的核心人工环节len(w) 2过滤单字因中文单字歧义极大如“行”“高”“低”在不同语境含义相反除非你明确需要否则一律剔除。2.3 TF-IDF向量化为什么max_features5000比10000更合理向量化是文本变数字的关键步。TfidfVectorizer参数直接影响后续聚类效果。对100份文件盲目设max_features10000会导致稀疏矩阵维度爆炸100×10000内存占用陡增大量低频词仅在1份文件出现引入噪声稀释核心主题信号聚类时距离计算受稀疏性干扰轮廓系数下降。我们通过词频统计经验阈值确定最优特征数from sklearn.feature_extraction.text import TfidfVectorizer import numpy as np # 先用CountVectorizer粗略统计词频找出高频词边界 from sklearn.feature_extraction.text import CountVectorizer cv CountVectorizer(max_features10000, stop_wordsenglish) word_counts cv.fit_transform(cleaned_docs) sum_words word_counts.sum(axis0) word_freq [(word, sum_words[0, idx]) for word, idx in cv.vocabulary_.items()] word_freq sorted(word_freq, keylambda x: x[1], reverseTrue) # 查看累计覆盖率前N个词覆盖多少总词频 cumsum np.cumsum([freq for _, freq in word_freq]) total cumsum[-1] coverage cumsum / total # 找到覆盖85%总词频的最小词数经验值100份文本85%足够 target_idx np.argmax(coverage 0.85) optimal_max_features min(5000, word_freq[target_idx][1]) # 上限5000防过拟合 print(f词频累计85%覆盖需 {target_idx1} 个词建议max_features{min(5000, target_idx1)}) # 正式TF-IDF向量化使用优化后的参数 vectorizer TfidfVectorizer( max_featuresmin(5000, target_idx 1), # 动态设定 ngram_range(1, 2), # 加入二元词组捕捉机器学习深度学习等固定搭配 min_df2, # 词至少在2份文档出现过滤孤例 max_df0.95, # 词在95%以上文档出现视为通用词如系统用户过滤 sublinear_tfTrue, # 使用log(tf1)缩放降低高频词权重 norml2 # L2归一化使余弦相似度计算更稳定 ) tfidf_matrix vectorizer.fit_transform(cleaned_docs) print(fTF-IDF矩阵形状{tfidf_matrix.shape}文档数 × 特征数) print(f特征词示例前10个{vectorizer.get_feature_names_out()[:10].tolist()})逻辑说明min_df2是硬性要求——100份文件中只出现1次的词大概率是拼写错误、人名、临时编号必须剔除max_df0.95比默认1.0更严格因为100份里95份都含的词如“问题”“解决”“系统”已失去区分度ngram_range(1,2)开启二元组对技术文档尤其重要“内存泄漏”比单独“内存”“泄漏”更有意义sublinear_tfTrue防止“的”“了”等超高频虚词主导向量这是很多新手忽略的玄学参数。3. 降维与聚类用UMAP替代t-SNE用HDBSCAN替代KMeans处理100份文本的分布特性3.1 为什么100份文本不适合直接KMeansHDBSCAN的3个不可替代优势KMeans要求预设簇数K而100份文本的主题数根本未知它强制所有点归属某簇但真实文本中必然存在“边缘样本”如一份混杂了售后技术咨询的工单。HDBSCANHierarchical Density-Based Spatial Clustering天然适配小批量文本分析无需指定K自动发现密度连通区域对100份文本的稀疏分布更鲁棒识别噪声点标记出无法归入任何主题的离群文档如一份纯表格数据的txt处理不规则簇形文本向量在高维空间本就非球形HDBSCAN基于密度而非距离更符合语义分布本质。但HDBSCAN直接作用于10000维TF-IDF矩阵会极慢必须先降维# 先用TruncatedSVD做初步线性降维快且保结构 from sklearn.decomposition import TruncatedSVD svd TruncatedSVD(n_components100, random_state42) tfidf_svd svd.fit_transform(tfidf_matrix) print(fSVD降维后形状{tfidf_svd.shape}) # 再用UMAP做非线性降维比t-SNE更适合小数据集 import umap umap_reducer umap.UMAP( n_neighbors15, # 小数据集用较小邻居数聚焦局部结构 n_components50, # 降至50维平衡速度与信息保留 min_dist0.1, # 控制簇间分离度0.1~0.5间调优 random_state42, metriccosine # 文本向量用余弦距离比欧氏更合理 ) tfidf_umap umap_reducer.fit_transform(tfidf_svd) print(fUMAP最终降维形状{tfidf_umap.shape})参数说明n_neighbors15对100个样本是黄金值——太大如50会模糊局部语义关系太小如5导致过度碎片化min_dist0.1让UMAP图中簇更紧凑便于后续HDBSCAN识别密度谷metriccosine强制UMAP用余弦距离因TF-IDF向量本身是方向敏感的长度代表文档长度非语义强度。3.2 HDBSCAN聚类设置min_cluster_size与min_samples的实操原则HDBSCAN两个核心参数决定结果质量min_cluster_size形成簇的最小样本数。对100份文本设为5是起点——低于5份的“主题”大概率是噪声或偶然重合min_samples控制簇核心点的密度要求。设为3即每个核心点需有3个邻居在ε半径内比默认5更宽松避免将合理小簇如“硬件故障”仅7份误判为噪声。我们用轮廓系数验证参数组合import hdbscan from sklearn.metrics import silhouette_score # 网格搜索min_cluster_size与min_samples param_grid { min_cluster_size: [3, 5, 8, 10], min_samples: [2, 3, 5] } best_score -1 best_params {} for mcs in param_grid[min_cluster_size]: for ms in param_grid[min_samples]: clusterer hdbscan.HDBSCAN( min_cluster_sizemcs, min_samplesms, metriceuclidean, # UMAP降维后可用欧氏 cluster_selection_methodeom # 更稳定的簇选择 ) labels clusterer.fit_predict(tfidf_umap) # 过滤掉噪声点label-1再算轮廓系数 mask labels ! -1 if mask.sum() 1: # 至少2个非噪声点才可算 score silhouette_score(tfidf_umap[mask], labels[mask]) if score best_score: best_score score best_params {min_cluster_size: mcs, min_samples: ms} print(f最优HDBSCAN参数{best_params}轮廓系数{best_score:.3f}) # 用最优参数执行最终聚类 final_clusterer hdbscan.HDBSCAN(**best_params, metriceuclidean, cluster_selection_methodeom) final_labels final_clusterer.fit_predict(tfidf_umap) n_clusters len(set(final_labels)) - (1 if -1 in final_labels else 0) n_noise list(final_labels).count(-1) print(f最终聚类结果{n_clusters} 个主题簇{n_noise} 个噪声文档)血泪经验cluster_selection_methodeomExcess of Mass比默认leaf更稳定尤其在小样本下min_cluster_size5是100份文本的“心理安全线”——少于5份的主题业务上通常不值得单独分析噪声文档label-1不要直接删除它们是质量检查的突破口如某份文件全是乱码或格式完全不符。3.3 可视化聚类结果用Plotly生成交互式2D散点图二维可视化必须保留语义可解释性。我们用UMAP的前两维直接绘图并标注簇中心和文档IDimport plotly.express as px import pandas as pd # 提取UMAP前两维用于绘图 umap_2d umap.UMAP(n_components2, random_state42, metriccosine).fit_transform(tfidf_svd) # 构建DataFrame viz_df pd.DataFrame({ x: umap_2d[:, 0], y: umap_2d[:, 1], cluster: final_labels, doc_id: doc_ids }) # 为噪声点单独设色 viz_df[cluster_label] viz_df[cluster].apply( lambda x: fCluster_{x} if x ! -1 else Noise ) # 绘图 fig px.scatter( viz_df, xx, yy, colorcluster_label, hover_data[doc_id], titlef100份文本UMAP聚类可视化{n_clusters}簇 {n_noise}噪声, width900, height600 ) # 标注簇中心计算每个簇的均值点 if n_clusters 0: for cluster_id in set(final_labels): if cluster_id -1: continue mask final_labels cluster_id center_x umap_2d[mask, 0].mean() center_y umap_2d[mask, 1].mean() fig.add_scatter( x[center_x], y[center_y], modemarkerstext, markerdict(size15, colorblack, symbolx), text[fC{cluster_id}], textpositiontop center, showlegendFalse ) fig.show()提示Plotly图可交互缩放、悬停查看doc_id方便业务人员快速定位某份文档属于哪个簇黑色X标记的簇中心是后续提取主题词的锚点见4.2节。4. 主题词提取与结果导出从向量空间反推每簇的语义标签4.1 为什么不能直接用TF-IDF词频排序用簇内加权平均向量更准对每个簇简单取该簇所有文档的TF-IDF向量平均值再按均值排序前10词比单纯统计词频更能反映“该簇专属语义”。因为高频但全簇共有的词如“系统”“用户”在均值中会被拉低某簇特有但绝对频次不高的词如“GPU显存”只在3份深度学习文档出现在均值中权重突出。代码实现import numpy as np def get_cluster_keywords(vectorizer, tfidf_matrix, labels, n_top_words10): 为每个簇提取最具代表性的关键词 feature_names vectorizer.get_feature_names_out() cluster_keywords {} for cluster_id in set(labels): if cluster_id -1: continue # 获取该簇所有文档的索引 cluster_mask labels cluster_id cluster_tfidf tfidf_matrix[cluster_mask] # 计算簇内TF-IDF均值向量 mean_vector np.asarray(cluster_tfidf.mean(axis0)).flatten() # 获取均值最大的n_top_words个词索引 top_indices mean_vector.argsort()[-n_top_words:][::-1] top_words [feature_names[i] for i in top_indices] top_scores [mean_vector[i] for i in top_indices] cluster_keywords[cluster_id] list(zip(top_words, top_scores)) return cluster_keywords cluster_keywords get_cluster_keywords(vectorizer, tfidf_matrix, final_labels) for cluster_id, keywords in cluster_keywords.items(): print(f\nCluster {cluster_id} 关键词TF-IDF均值降序) for word, score in keywords: print(f {word}: {score:.4f})注意np.asarray(...).flatten()是必须的——scipy.sparse矩阵的.mean()返回matrix类型需转为ndarray才能用argsort[::-1]倒序确保最高分在前分数保留4位小数方便对比词间差异如0.1234vs0.0987。4.2 生成带主题标签的Excel报告包含原始文件路径、簇ID、关键词、人工备注列最终交付物必须让非技术人员也能用。我们导出Excel含4个工作表Summary各簇文档数、关键词、典型文件示例All_Documents100份文件完整清单含簇ID、原始路径、清洗后首50字Noise_Docs所有噪声文档详情供质量复核Keyword_Matrix各簇关键词共现热力图可选。核心代码All_Documents表import pandas as pd # 构建主报告DataFrame report_data [] for i, (doc_id, text) in enumerate(zip(doc_ids, cleaned_docs)): label final_labels[i] cluster_label fCluster_{label} if label ! -1 else Noise # 截取清洗后文本前50字作预览 preview text[:50] ... if len(text) 50 else text report_data.append({ File_ID: doc_id, Cluster_ID: label, Cluster_Label: cluster_label, Text_Preview: preview, Original_Path: str(Path(data/raw_texts) / doc_id), # 还原绝对路径供定位 Keywords: , .join([kw for kw, _ in cluster_keywords.get(label, [])[:3]]) if label ! -1 else }) report_df pd.DataFrame(report_data) # 导出Excel自动分Sheet with pd.ExcelWriter(text_mining_report_100files.xlsx, engineopenpyxl) as writer: # Summary Sheet summary_data [] for cluster_id, keywords in cluster_keywords.items(): doc_count (final_labels cluster_id).sum() top3 , .join([kw for kw, _ in keywords[:3]]) summary_data.append({ Cluster_ID: cluster_id, Document_Count: doc_count, Top_Keywords: top3, Sample_File: next((d[File_ID] for d in report_data if d[Cluster_ID]cluster_id), ) }) pd.DataFrame(summary_data).to_excel(writer, sheet_nameSummary, indexFalse) # All Documents Sheet report_df.to_excel(writer, sheet_nameAll_Documents, indexFalse) # Noise Docs Sheet noise_df report_df[report_df[Cluster_ID] -1].copy() noise_df.to_excel(writer, sheet_nameNoise_Docs, indexFalse) # Keyword Matrix (optional) if cluster_keywords: # 构建关键词-簇矩阵 all_keywords list(set(kw for kw_list in cluster_keywords.values() for kw, _ in kw_list[:5])) keyword_matrix pd.DataFrame(0, indexall_keywords, columnslist(cluster_keywords.keys())) for cluster_id, kw_list in cluster_keywords.items(): for kw, score in kw_list[:5]: if kw in all_keywords: keyword_matrix.loc[kw, cluster_id] score keyword_matrix.to_excel(writer, sheet_nameKeyword_Matrix) print(✅ Excel报告已生成text_mining_report_100files.xlsx)提示Original_Path列写入绝对路径方便双击打开原始文件Text_Preview用清洗后文本非原始确保预览内容干净可读Keywords列只取每簇前3词避免Excel单元格过长。5. 避坑指南100份文本分析中sklearn流水线的5个致命陷阱与解法5.1 现象TF-IDF向量化后矩阵全为0get_feature_names_out()返回空列表原因清洗函数clean_and_tokenize()返回空字符串或所有文档清洗后长度2如全是单字或标点。TfidfVectorizer默认min_df1但若所有文档都无有效词则无特征可提取。解决在clean_and_tokenize()结尾加assert len(words) 0, f文档清洗后为空{text[:30]}...运行前用print([len(t) for t in cleaned_docs[:5]])检查清洗后长度分布。5.2 现象HDBSCAN聚类全部返回-1全为噪声原因UMAP降维参数min_dist过大如设为0.8导致所有点被强行推开密度无法连通或n_neighbors过小如设为3使局部邻域失效。解决重跑UMAPmin_dist改为0.01~0.3区间n_neighbors设为max(5, int(len(cleaned_docs)*0.1))100份即10用umap.plot.connectivity(umap_reducer, show_pointsTrue)查看连通性图。5.3 现象聚类结果中某簇关键词全是“的”“了”“在”但清洗时已加停用词原因TfidfVectorizer的stop_words参数与自定义清洗冲突——若你在清洗时没过滤这些词而vectorizer又没设stop_words它们仍会进入向量反之若清洗已过滤vectorizer再设stop_words会冗余但无害。解决只在清洗层过滤TfidfVectorizer中stop_wordsNone默认避免双重逻辑用print(vectorizer.get_stop_words())确认是否为空。5.4 现象hdbscan.HDBSCAN报MemoryError或运行超10分钟原因直接对高维TF-IDF矩阵如100×10000运行HDBSCAN计算pairwise距离矩阵需O(n²)内存。100份看似小但10000维下距离矩阵占100²×8bytes80KB可接受但若误设max_features50000则升至2MB某些环境仍会OOM。解决必须先降维SVD→UMAP且UMAP.n_components ≤ 100或改用algorithmprims_kdtree需pynndescent库加速。5.5 现象导出的Excel中Cluster_ID列全为-1但Summary表显示有多个簇原因final_labels是HDBSCAN输出的numpy数组report_df构建时i循环索引与final_labels长度不一致——可能因清洗后cleaned_docs数量 doc_ids如某文件清洗为空被跳过但final_labels仍按cleaned_docs长度生成导致索引错位。解决严格保证len(cleaned_docs) len(final_labels)在聚类前加断言assert len(cleaned_docs) len(final_labels)构建report_data时用zip(cleaned_docs, final_labels, doc_ids)而非range(len(...))。6. 进阶技巧用主题一致性评分C_v验证关键词质量以及自动化更新停用词表6.1 为什么轮廓系数不够用C_v评分量化主题可解释性轮廓系数衡量簇间分离度但不保证簇内语义一致。C_vCoherence Value专为LDA/HDBSCAN主题评估设计计算公式为$$ C_v \frac{1}{M} \sum_{m1}^{M} \frac{1}{N_m} \sum_{n1}^{N_m} \log \frac{P(w_n, w_{n1}) \epsilon}{P(w_n) P(w_{n1})} $$其中 $w_n$ 是第n个关键词$P$ 是文档中共现概率$\epsilon$ 是平滑项。sklearn不内置但我们用轻量实现from collections import defaultdict, Counter import math def calculate_cv_score(cleaned_docs, labels, cluster_keywords, top_k10, epsilon1e-12): 计算每个簇的C_v一致性得分 cv_scores {} # 构建文档-词集合去重 doc_word_sets [set(doc.split()) for doc in cleaned_docs] for cluster_id, keywords in cluster_keywords.items(): if cluster_id -1: continue # 获取该簇所有文档的词集合 cluster_mask labels cluster_id cluster_docs [doc_word_sets[i] for i in range(len(labels)) if cluster_mask[i]] # 提取top_k关键词去停用后 kw_list [kw for kw, _ in keywords[:top_k]] if len(kw_list) 2: cv_scores[cluster_id] 0.0 continue # 计算共现频率 cooccur defaultdict(int) word_freq Counter() for doc_set in cluster_docs: for kw in kw_list: if kw in doc_set: word_freq[kw] 1 for other_kw in kw_list: if other_kw ! kw and other_kw in doc_set: cooccur[(kw, other_kw)] 1 # 计算C_v score 0.0 for i, kw1 in enumerate(kw_list): for j, kw2 in enumerate(kw_list): if i j: p_co cooccur[(kw1, kw2)] / len(cluster_docs) if cluster_docs else 0 p1 word_freq[kw1] / len(cluster_docs) if cluster_docs else 0 p2 word_freq[kw2] / len(cluster_docs) if cluster_docs else 0 score math.log((p_co epsilon) / (p1 * p2 epsilon)) cv_scores[cluster_id] score / (len(kw_list) * (len(kw_list)-1) / 2) if len(kw_list) 1 else 0.0 return cv_scores # 计算并打印 cv_scores calculate_cv_score(cleaned_docs, final_labels, cluster_keywords) print(\n各簇C_v一致性得分越高越 coherent) for cluster_id, score in cv_scores.items(): print(f Cluster {cluster_id}: {score:.4f})参数说明epsilon1e-12防止log(0)top_k10与关键词提取一致得分0.4为良好0.6为优秀。若某簇得分0.2说明关键词不聚焦需检查该簇文档是否混杂如同时含“退货”和“开发需求”应人工拆分或复查清洗逻辑。6.2 自动化更新停用词表基于C_v低分簇的逆向挖掘C_v低分簇暴露了当前停用词表的盲区。我们提取这些簇中高频但跨簇出现的词加入停用def auto_update_stopwords(cleaned_docs, labels, cv_scores, low_cv_threshold0.25, min_global_freq5): 自动发现新停用词低C_v簇中高频且全局出现的词 # 找出低C_v簇 low_cv_clusters [cid for cid, score in cv_scores.items() if score low_cv_threshold] if not low_cv_clusters: print(✅ 无低C_v簇无需更新停用词) return set() # 统计低C_v簇内所有词频 low_cv_words Counter() for i, (doc, label) in enumerate(zip(cleaned_docs, labels)): p a hrefhttps://download.csdn.net/download/weixin_44510615/20080418 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p