尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

中文LDA主题模型实战:jieba分词与gensim建模全流程

中文LDA主题模型实战:jieba分词与gensim建模全流程 简介基于Python gensim库的LDA中文文本分析代码包专门解决LDA算法处理中文语料时参考匮乏的问题为中文主题挖掘提供了可复用的实现思路。资源面向需要完成课程设计、毕业设计或入门主题建模的Python开发者尤其适合手中只有英文示例、难以迁移到中文场景的学习者可直接借鉴其分词、过滤与LDA训练环节。压缩包共8个文件包含3个Python脚本主题模型、TF-IDF与去停用词处理、3个UTF-8编码的txt示例数据、1个README说明文档及1个Git忽略文件整体大小约953KB结构简洁便于快速定位关键代码。已有303人浏览学习属于小而实用的中文NLP实践资源。下载后可通过说明文档了解环境配置Anaconda与gensim代码已运行通过可参考其中的中文分词、停用词过滤、TF-IDF计算与LDA主题提取完整流程在此基础上修改语料即可迁移到其他文本分析任务亦可进一步进行主题可视化与结果解读。1. 中文LDA文本分析为什么绕不开jieba分词网上用gensim做LDA主题模型的教程十有八九拿英文语料演示。原因很简单英文单词天然以空格分隔直接str.split()就能得到词序列。中文不行“环境监测与环境影响评价”这句话如果不分词gensim会把它当成“环”“境”“监”“测”这些单字的集合主题语义直接碎掉。所以中文LDA必须先过jieba分词这一关再搭配去停用词才能作为gensim的输入。这套源码包里正好提供了完整链路qutingci.py负责分词去停用词topicmodel3.py做LDA训练topicmodel TF IDF.py演示TF-IDF加权流程还附带1893个中文停用词和一份环境监测摘要语料。目前网上类似的中文LDA源码很少这份代码跑通后适合改成自己的文本做课程设计或毕业设计。2. 语料预处理jieba分词与停用词过滤的正确姿势2.1 为什么先分词中文LDA与英文LDA的本质差异英文LDA教程里最常见的一行代码是text.split()因为英文文本词和词之间天然有空格。中文文本没有这种天然间隔“环境监测与影响评价”整体是一串连续字符。如果直接把这串字符交给gensim的Dictionary它会把每个汉字当作独立的token去统计一顿操作之后得到的是“字频矩阵”而不是“词频矩阵”。举个例子“环境”这个词在语料里出现100次在未分词的情况下会被拆成“环”和“境”各100次两个字的出现位置还经常错开LDA根本没机会把“环”和“境”组合成一个语义单元。jieba分词的原理包括基于前缀词典构建有向无环图然后采用动态规划查找最大概率路径对未登录词用隐马尔可夫模型补充。这样“环境监测”会作为一个词被切出来而不是被拆成“环境/监测”。分词粒度直接决定LDA的特征空间。如果你处理的文本里专业术语很多比如“VOCs”、“PM2.5”这种英文和数字混合词建议先向jieba添加自定义词典否则会被切得乱七八糟。2.2 停用词表的选择与自建扩展语言里大量虚词和助动词对主题识别没有意义“的、了、是、在、和、与、及”等词在几乎每篇文档里都会出现它们拉高了词频却没有主题区分度。这套资源附带的stopWords 1893utf8.txt已经收录了常见中文停用词但通用停用词表面对具体领域时往往不够用。以环境监测摘要为例“影响”、“分析”、“研究”这类词很可能出现在所有文档里导致所有主题都含有这些词主题之间无法有效区分。这时候需要把这类领域高频词也加进停用词表。停用词表是纯文本格式每行一个词保存为UTF-8编码。自己在资源里那份1893词的基础上追加新词时注意用记事本另存为UTF-8不要用ANSI否则读取时会出现乱码。加载后可以对高频词做个快速对比原始文本jieba分词结果过滤停用词数字后主要分析了2019年的大气污染物浓度变化主要 / 分析 / 了 / 2019年 / 的 / 大气污染 / 物 / 浓度 / 变化大气污染 / 物 / 浓度 / 变化注意“大气污染物”被jieba切成了“大气污染”和“物”如果想保持完整可以加自定义词。2.3 分词与停用词过滤实现qutingci.py2.3.1 读取停用词表qutingci.py的第一步是加载停用词表。文件读取时必须指定UTF-8编码这是中文处理最常见的坑。代码如下def load_stopwords(path): stopwords set() with open(path, r, encodingutf-8) as f: for line in f: word line.strip() if word: stopwords.add(word) return stopwords这里使用set存储停用词集合的成员判断平均时间复杂度是O(1)比使用list后in判断快得多。line.strip()会去掉行尾的换行符和首尾空格if word过滤掉空行。使用encodingutf-8保证兼容中文。2.3.2 分词并过滤接着是分词函数。下载包里的qutingci.py核心流程大致如下import jieba def cut_and_filter(text, stopwords): words jieba.cut(text) filtered [] for word in words: word word.strip() if len(word) 0: continue if word.isdigit(): continue if word in stopwords: continue filtered.append(word) return filteredjieba.cut返回一个生成器逐个产出分词结果好处是大文本处理时不会一次性占满内存。过滤顺序建议先做空串判断再查停用词最后滤数字。这里滤掉纯数字是为了避免年份、编号等对主题建模造成干扰。如果你的文本中数字有实际含义比如“PM2.5”这种需要你在自定义词典里把它定义为一个整体否则同样会被过滤。2.3.3 遍历语料文件前面的函数只处理单个文本还需要读取语料文件。资源里“环境监测与环境影响评价摘要utf8).txt”这份摘要文件通常按行存放即每行是一篇文档的摘要。常见做法是逐行读取def main(): stopwords load_stopwords(stopWords 1893utf8.txt) docs [] with open(环境监测与环境影响评价摘要utf8).txt, r, encodingutf-8) as f: for line in f: text line.strip() if text: words cut_and_filter(text, stopwords) docs.append(words) # 保存到文件 with open(corpus.txt, w, encodingutf-8) as out: for doc in docs: out.write( .join(doc) \n) print(fprocessed {len(docs)} docs)注意如果原始文件里每个摘要内部有换行就需要以空行或某个标记分隔。实际操作时可以先把语料清理成每行一篇再跑脚本。2.4 验证分词质量预处理完成后不要着急进LDA先输出几条结果看看。快速验证的方式是统计高频词from collections import Counter from itertools import chain all_words list(chain.from_iterable(docs)) common Counter(all_words).most_common(20) print(common)如果前20个词里还有“我们”“进行”“通过”这类停用词说明停用词表还需要补。如果出现了大量单字词比如“的”“在”没滤干净就要检查停用词表是否加载成功。另一个加分项是检查文档长度分布print([len(d) for d in docs])太短的文档少于10个词对LDA几乎没有统计意义可以删除或合并。3. 基于gensim构建TF-IDF与LDA主题模型3.1 从分词结果到词袋向量gensim的输入是“语料列表”列表中每个元素是一篇文档文档本身是Token列表。你需要先把预处理结果构建成词典和向量。from gensim import corpora from gensim.models import TfidfModel, LdaModel # docs 是上一阶段得到的分词结果形如 [[环境监测,数据], ...] dictionary corpora.Dictionary(docs) # 去除极端词出现次数少于5次或出现在超过50%的文档中 dictionary.filter_extremes(no_below5, no_above0.5) dictionary.compactify() # 将每篇文档转换为词袋向量 corpus_bow [dictionary.doc2bow(doc) for doc in docs]Dictionary会为每个不同的词分配一个整数ID。filter_extremes(no_below5, no_above0.5)表示只保留至少在5篇文档中出现过、且出现在不超过一半文档中的词。no_below可以剔除只在极个别文档出现的噪声词no_above可以剔除几乎每篇都出现的通用词这类词通常在预处理阶段没清干净。compactify()用于重新映射ID去除被过滤掉词的ID空洞。如果某篇文档很长词频会急剧放大后续可以配合TF-IDF处理。3.2 TF-IDFtopicmodel TF IDF.py在做什么下载包里单独放了一个topicmodel TF IDF.py它演示的是先用TF-IDF加权再用加权后的向量训练LDA。传统LDA使用的是词频向量但词频向量中高频无意义词仍然可能占据主导。TF-IDF的目标是突出文档区分度高的词。# 训练TF-IDF模型 tfidf_model TfidfModel(corpus_bow) # 将整个词袋向量集转换为TF-IDF向量集 corpus_tfidf tfidf_model[corpus_bow]这里tfidf_model[corpus_bow]并不是一次性把所有向量加载到内存而是返回一个惰性生成器每次迭代时才计算。在你后续训练LDA时gensim会按需计算。如果你重复使用corpus_tfidf可能在多轮遍历时反复执行TF-IDF转换性能会受影响。稳妥做法是一次性转成列表或使用gensim.matutils做一些缓存。关于是否要用TF-IDF做LDA业界有分歧。gensim官方文档里的LDA教程通常直接用词袋向量。但我在处理短文本时发现TF-IDF加权后得到的主题词更容易聚焦到长尾关键词代价是部分主题的可读性下降。所以这个脚本给了你多一种选择可以对照着看。3.3 LDA核心参数设置与训练3.3.1 基础训练代码资源里的topicmodel3.py核心训练部分大致如下lda_model LdaModel( corpuscorpus_tfidf, num_topics8, id2worddictionary, passes15, iterations50, random_state42, alphaauto, etaauto )corpus可以传词袋向量也可以传TF-IDF向量两者实验下选效果更好的。num_topics8表示期望从语料中提取出8个主题。这个值需要反复实验调整通常根据文档量和业务先设定一个范围。passes是训练时遍历语料的次数相当于所有文档被处理多少轮数值越大收敛越充分但训练时间线性上升。iterations是每一轮中Gibbs采样的迭代次数上限。alpha和eta分别控制文档-主题分布和主题-词分布的先验auto让模型自己学习适合没有先验知识的场景。3.3.2 参数对结果的影响速查参数作用经验值/说明num_topics主题数量5~30需对比实验passes语料遍历次数10~20小时语料可更大iterations每次遍历中的迭代次数50~200alpha文档-主题先验auto或1/num_topicseta主题-词先验auto或1/num_topicsrandom_state随机种子固定后结果可复现有个容易忽略的点passes和iterations不是越大越好。过大的iterations会造成过拟合使主题词集中在个别文档上。我一般先用小数据集粗调num_topics固定passes10等主题数选定后再加大passes做最终训练。3.4 训练结果输出训练完成后需要把主题词打印出来或者存成文件for topic_id in range(lda_model.num_topics): topic_terms lda_model.show_topic(topic_id, topn10) terms [term for term, prob in topic_terms] print(fTopic {topic_id}: {, .join(terms)}) # 保存模型 lda_model.save(lda_model.model)show_topic(topic_id, topn10)返回该主题概率最高的10个词及对应概率。打印出来之后对照你所在领域的背景知识去判断每个主题是否代表一类可解释的主题比如“空气质量”、“水污染”、“噪声监测”等。如果某个主题的10个词无法用一个概念概括说明这个主题是混合主题可能是num_topics设置过多或过少造成的。4. 主题数与可解释性评估与迭代LDA最折磨人的地方在于num_topics要自己定。网上有人推荐直接用主题数文档数开方那只是糙算。实际工程流程是先跑一组候选主题数从困惑度和人工判读两个维度评估。4.1 用log_perplexity做客观度量gensim的LdaModel自带log_perplexity(corpus)方法可以返回对数困惑度。困惑度越低模型对新样本的预测能力越强。但gensim返回的是log_perplexity即负的对数困惑度所以返回值越大负数中越大比如-7.5比-8.0大表示模型越好。注意这个指标对主题数并不总是单调且容易偏向更多主题。常见的做法是用小批量子集评估否则在大语料上计算时间太长。log_p lda_model.log_perplexity(corpus_tfidf) print(log_p)4.2 候选主题数循环实验做一个循环训练多个LDA模型比较困惑度topics_range [5, 10, 15, 20] results {} for k in topics_range: lda_k LdaModel(corpus_tfidf, num_topicsk, id2worddictionary, passes15, random_state42) log_p lda_k.log_perplexity(corpus_tfidf) results[k] log_p print(fK{k}, log_perplexity{log_p:.3f}) # 单独保存每个模型后面人工检查 lda_k.save(flda_k{k}.model)这里固定random_state42是必须的否则每次随机初始化不同无法比较。假设输出如下主题数log_perplexity主观可解释主题数5-7.865410-7.214915-6.903720-6.5525从上表可以看出困惑度随主题数增加而下降但20个主题时只有5个能解释其余主题重复或碎片化。所以不能只看困惑度还要结合人工判读。4.3 人工判读与主题词表导出人工判读的方法很朴素把每个主题的前15个词列出来逐行看。用程序全部导出到CSV更方便import csv with open(topics_compare.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([num_topics, topic_id, terms]) for k in topics_range: lda_k LdaModel.load(flda_k{k}.model) for tid in range(k): terms [term for term, prob in lda_k.show_topic(tid, topn15)] writer.writerow([k, tid, .join(terms)])用Excel打开后把同一主题数下的8-10行主题词放在一起看。如果两个主题的前15个词中有8个以上重叠说明主题数过多如果某个主题词是“环境、评价、影响、分析、数据、监测、研究”几乎覆盖了所有文档的通用词那它是其余主题的公共底座真正的主题被它占据了。这时需要在预处理阶段增加停用词或者在建模时提高eta中的词权重惩罚但更简单的做法是扩大主题数让公共底座被拆开。4.4 主题-文档分布检查除了主题词还需要看每篇文档在主题上的分布是否合理。LDA会给每篇文档生成一个主题概率向量get_document_topics可以取到doc_bow dictionary.doc2bow(docs[0]) doc_topics lda_model.get_document_topics(doc_bow) print(doc_topics)输出形如[(0, 0.85), (3, 0.1), ...]。如果所有文档都被分到一个主题上说明语料本身主题单一或者主题数较大模型退化为聚类。此时需要降低主题数或检查语料来源是否均衡。5. 进阶中文LDA落地时的调优经验与验证技巧5.1 固定随机种子保证结果可复现LDA是一种随机算法初始化会影响最终结果。每次运行结果不一致对于毕业设计这种需要答辩复现的场景非常致命。解决方法是在LdaModel里设置random_state42一个数值只要固定即可。同时如果你用lda_model.save保存了模型加载后直接预测不需要重新训练。5.2 对新文档做主题推断训练好的模型要能对新摘要做主题推断。新文档必须走完全相同的预处理链路jieba分词、过滤停用词、文档向量化。注意必须使用训练时的dictionary因为模型只认识训练过的词的ID。示例new_text 新来的环境监测报告显示该区域水质明显改善 new_words cut_and_filter(new_text, stopwords) new_bow dictionary.doc2bow(new_words) new_topics lda_model.get_document_topics(new_bow)new_bow中会忽略词典外的词所以新文本里如果出现大量未登录词主题推断效果会差。可以在cut_and_filter阶段加载与训练时相同的自定义词典。5.3 pyLDAvis可视化辅助判断想直观确认主题间的可分性可以用pyLDAvis。gensim 4.x版本中的导入方式已经变化import pyLDAvis.gensim_models as gensimvis import pyLDAvis vis_data gensimvis.prepare(lda_model, corpus_tfidf, dictionary) pyLDAvis.save_html(vis_data, lda_vis.html)打开HTML可以看到左侧主题气泡。理想状态下气泡互相分开且大小均衡如果多个气泡大面积重叠说明主题区分度差需要调整主题数。注意prepare的第2个参数要传训练时用的语料向量如果用词袋训练的LDA就传corpus_bow不要混用TF-IDF。5.4 模型容量与资源限制目前项目里的源码在小语料几百篇上运行没有问题。如果你的语料上万篇单机训练会很吃力。常见做法是先用gensim的MmCorpus将语料序列化到磁盘然后分批流式加载或者升级使用lda_multicore并行版本。这些进阶手段等你跑通当前流程后再考虑否则调参时不断加载大文件会让你怀疑人生。本文还有配套的精品资源点击获取
返回列表