ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LDA主题模型实战:用Python分析豆瓣《庆余年》长评论的完整流程

LDA主题模型实战:用Python分析豆瓣《庆余年》长评论的完整流程 简介这是一份基于LDA主题模型的豆瓣长评论挖掘与分析资源包完整收录Python源码、数据集与配套说明文档面向高校计算机相关专业学生及自然语言处理入门者。资源内置数据清洗、主题数困惑度评估、gensim建模、词云绘制等核心模块并以可视化图表呈现主题一致性、困惑度及高频词汇分布可直接复现从文本预处理到主题聚类结果输出的全过程。包内共39个文件以7个py脚本为主体辅以16张结果分析png图片、8个txt文本说明与词典、4个csv数据表以及xlsx、ttf字体等辅助文件压缩包约12.53MB。项目代码经过严格测试能平稳运行适合作为毕业设计、课程设计或期末作业的完整选题方案也便于初学者对照学习LDA建模与短文本分析流程。目前已有92人浏览学习下载后可按需修改功能或扩展新的语料场景。1. LDA 模型跑豆瓣《庆余年》长评论这份 Python 源码包到底能拆出什么豆瓣长评论动辄几百字藏着观众对剧情、角色、服化道的真实态度但几百条评论堆在一起人工根本读不完。LDALatent Dirichlet Allocation主题模型正是干这个的把每一条评论看成若干主题的混合再把每个主题看成若干词语的分布跑完就能看到“大家到底在聊哪几个话题”。这份资源就是一个可直接运行的豆瓣长评论 LDA 分析项目代码覆盖了从数据清洗、分词、停用词过滤到主题数选取、模型训练、困惑度与一致性评估、词云与热力图可视化的完整链路。我拆解后认为它最适合两类人一是做毕业设计需要“能跑、有图、有分析结论”的计算机相关专业学生二是想用最短时间把 LDA 流程跑通、再替换成自己数据的 NLP 入门者。下面按我自己的复现顺序讲清楚每个脚本的作用、参数怎么调、以及哪些地方容易翻车。2. 先解决数据与中文分词庆余年.csv 到干净语料的全流程2.1 数据文件里有什么先盘点再动手解压后 data 目录里躺着几个关键文件庆余年.csv 是原始评论数据person.txt 是人名表stopwords.txt 是中文停用词表kaiti.TTF 是楷体字体给词云图用的data.xlsx 是评论数据的 Excel 备份。output 目录则是运行后的产物包括 lda_topic_coherence.png、topic_perplexity.png、dy.png、词云图、topic_heatmap_comm15.png 等。先看 CSV 的字段结构我惯用的做法是用 pandas 打开看看import pandas as pd df pd.read_csv(data/庆余年.csv, encodingutf-8) print(df.columns.tolist()) print(df.head()) print(df.shape)逻辑说明第一行打印字段名第二行打印前 5 条第三行打印总行数。这样能快速确认有没有评论内容字段、字段名叫什么、数据量级是多少。参数说明encoding 根据实际情况调整如果 CSV 是 GBK 编码保存的需要改成 encodinggbk否则会直接 UnicodeDecodeError。文本列名不同时后续所有脚本里读列的字段名都要对应替换。2.2 分词的坑jieba 词表、自定义词和停用词一个都不能少中文 LDA 和英文最大的区别在于必须先分词。英文天然按空格切中文必须靠分词工具。这里用的是 jieba代码里通常长这样import jieba # 加载自定义人名提高人名识别准确率 with open(data/person.txt, r, encodingutf-8) as f: person_names [line.strip() for line in f.readlines()] for name in person_names: jieba.add_word(name) # 加载停用词表 with open(data/stopwords.txt, r, encodingutf-8) as f: stopwords set(line.strip() for line in f.readlines()) # 分词并过滤停用词 def tokenize(text): words jieba.lcut(str(text)) return [w for w in words if w.strip() and w not in stopwords and len(w) 1]逻辑说明先把 person.txt 里的人名逐行加入 jieba 自定义词典这样“范闲”“庆帝”“陈萍萍”不会被错误切开再把停用词表读成集合分词后把停用词、空字符串、单字全部过滤掉。参数说明len(w) 1 是为了过滤单个字因为单字在主题模型里基本都是噪音。如果评论里有大量英文或数字还要补一个 isalpha 判断或者正则过滤。person.txt 是可变参数换成自己数据集时把对应的人名或专有名词写进去就行。这里最值得注意的坑是LDA 训练用的语料必须是分词后的结果而不是原始句子。如果直接把“我喜欢看范闲”整句丢给模型模型会把整句话当成一个 token主题完全没法看。分词做得好不好直接决定后面主题词的质量。2.3 构建词典与语料从分词结果到 BOW 向量gensim 的 LDA 接口不接收字符串列表它要求先建词典Dictionary再把每个文档转成词袋Bag of Words向量from gensim.corpora import Dictionary # 假设 texts 是分词后的列表每个元素是一条评论的分词结果 dictionary Dictionary(texts) # 过滤极端词只在极少数文档出现的词和出现过多的词都去掉 dictionary.filter_extremes(no_below5, no_above0.5) # 每个文档转成 (词id, 词频) 的稀疏向量 corpus [dictionary.doc2bow(text) for text in texts] print(词典大小:, len(dictionary)) print(语料文档数:, len(corpus))逻辑说明Dictionary(texts) 会给每个词分配一个整数 id。filter_extremes 的作用是裁剪词典no_below5 表示在少于 5 篇文档中出现的词会被丢弃no_above0.5 表示在超过 50% 文档中都出现的词会被丢弃这种词通常是“剧情”“觉得”“真的”这类对主题区分毫无贡献的词。参数说明no_below 和 no_above 是 LDA 预处理阶段最值得调的两个参数。评论数量少时把 no_below 调低到 2~3评论数量多时可以调高到 10 以上。no_above 一般设在 0.3~0.7 之间太高会让主题词全是常用词太低会把一些有价值的低频词也滤掉。3. 主题数 k 怎么定perplexity.py 的困惑度曲线与我的实测结果3.1 为什么不能拍脑袋定主题数困惑度图告诉你答案LDA 里最棘手的问题不是训练本身而是主题数 k 该设多少。k 太小所有内容挤在一个主题里k 太大主题之间高度重叠。perplexity.py 就是用来解决这个问题的——它循环训练不同 k 值的模型计算每个模型的困惑度最终画出一条曲线。困惑度Perplexity是 gensim 里衡量模型对语料拟合程度的指标值越低通常代表模型泛化能力越好。但注意它只是参考不是绝对真理因为低困惑度也可能意味着过拟合。实际项目中我一般把困惑度曲线和主题可解释性结合起来判断。perplexity.py 的核心逻辑from gensim.models import LdaModel import numpy as np def compute_perplexity(dictionary, corpus, texts, min_k3, max_k20): perplexity_scores [] for k in range(min_k, max_k 1): model LdaModel( corpuscorpus, id2worddictionary, num_topicsk, random_state42, passes20 ) # 计算在训练语料上的困惑度 perp model.log_perplexity(corpus) perplexity_scores.append(np.exp(-perp)) print(fk{k}, perplexity{perplexity_scores[-1]:.2f}) return perplexity_scores逻辑说明循环从 3 到 20 依次训练模型用一个固定随机种子保证结果可复现。log_perplexity 返回的是对数困惑度取负再指数化就是数值形式的困惑度。数值越小说明模型对语料的解释能力越强。参数说明min_k 和 max_k 的范围要看语料量级。几百条评论时3~20 是合理的搜索范围几千条以上可以试 5~50。passes20 控制模型遍历语料的次数值越大训练越充分但耗时越长小语料 20~50 次足够。random_state 固定成 42 很重要否则每次运行结果都不一样画出的曲线会抖动。运行后观察曲线通常会看到一个“拐点”——困惑度先快速下降之后趋于平缓甚至回升。拐点附近的 k 值就是优先候选。另外官方包里还提供了一份 gensim_perplexity.csv记录了 gensim 在不同 k 下的困惑度值可以直接画出曲线不用重新训练。3.2 困惑度的局限性为什么我还会再看一眼一致性分数困惑度低的模型不一定主题可读。包里的 coherence.py 用主题一致性Coherence Score来弥补这个缺陷它衡量主题内部词语的语义相关程度分数越高主题越可解释。from gensim.models.coherencemodel import CoherenceModel def compute_coherence(dictionary, corpus, texts, k): model LdaModel(corpuscorpus, id2worddictionary, num_topicsk, random_state42) cm CoherenceModel(modelmodel, textstexts, dictionarydictionary, coherencec_v) return cm.get_coherence() # 对多个 k 计算一致性 for k in range(3, 21): score compute_coherence(dictionary, corpus, texts, k) print(fk{k}, coherence{score:.4f})逻辑说明CoherenceModel 传入训练好的模型、原始分词文本和词典coherencec_v 使用 c_v 算法该算法的核心思想是基于词共现统计来计算主题词的语义一致性。注意 texts 必须是分词后的列表不是原始字符串。参数说明coherence 参数还可以换成 u_mass但 u_mass 对低频词敏感容易出负分c_v 是实践中最常用的选择。实际选 k 时我把困惑度曲线和一致性曲线画在同一个坐标系里选两者平衡的点——通常是困惑度已经进入平台期、一致性开始走平或刚要下降的前一个 k 值。我的实测经验是这份豆瓣《庆余年》评论数据的 k 值在 8~12 之间比较合适。k5 时主题太少“演技”和“剧情”会揉在一起k15 以上时会出现多个主题词高度重叠、肉眼难以区分的“僵尸主题”。如果时间有限直接取 k10 用默认参数跑一遍看主题词再微调这比死磕曲线更快。4. 核心训练脚本 lda_model_gensim.py参数、输出文件与一键复跑4.1 模型训练的主入口默认参数怎么配的lda_model_gensim.py 是整个项目的核心脚本它把前面几步串成一条流水线读数据 → 分词 → 建词典 → 过滤 → 转 BOW → 训练 LDA → 输出主题词和可视化图。我用编辑器打开后把关键逻辑梳理成了下面这份可读性更高的等价版本from gensim.models import LdaModel from gensim.corpora import Dictionary # 上一步准备好的变量dictionary, corpus, texts lda LdaModel( corpuscorpus, id2worddictionary, num_topics10, # 主题数前面困惑度曲线选出来的 alphaauto, # 文档-主题分布超参数让模型自动学习 etaauto, # 主题-词分布超参数让模型自动学习 passes30, # 整个语料遍历次数 iterations300, # 每次遍历内部的采样迭代次数 random_state42, # 固定随机种子保证复现 per_word_topicsTrue # 记录每个词的所属主题做可视化用 ) # 打印每个主题的前 15 个词 for idx, topic in lda.print_topics(num_words15): print(fTopic {idx}: {topic})逻辑说明alpha 和 eta 分别控制文档-主题分布和主题-词分布的稀疏程度。设成 auto 让模型根据语料自动学习这两个值在数据量足够时自适应效果通常优于手动指定。passes 和 iterations 控制模型收敛质量值越大主题越稳定但训练时间线性增长。参数说明如果你的数据量只有几百条评论passes30 可能在 1~2 分钟内跑完如果数据量上万条建议先测一小部分数据确认速度没问题再全量跑。per_word_topicsTrue 是给 pyLDAvis 可视化做准备的如果不需要可视化可以去掉以节省内存。4.2 output 目录里的那些图到底是怎么生成的运行完训练脚本output 目录里会生成一批图片。这些图各自回答不同的问题输出文件回答的问题对应脚本逻辑topic_perplexity.png / topic_likehood.png困惑度/似然度随 k 的变化perplexity.pylda_topic_coherence.png主题一致性随 k 的变化coherence.py词云图word_cloud每个主题下哪些词出现频率最高word_cloud.pytopic_heatmap_comm15.png评论与主题之间的分布热度topic_heatmap 相关逻辑doc_top_topic每条评论最可能归属的主题模型 doc2bow 后取 argmax词云图是毕业设计里最直观的加分项word_cloud.py 里关键逻辑from wordcloud import WordCloud import matplotlib.pyplot as plt # 取出某个主题的词分布例如 topic_id0 topic_words dict(lda.show_topic(0, topn50)) # 生成词云图注意指定中文字体 wc WordCloud( font_pathdata/kaiti.TTF, # 指定楷体否则中文会显示成方块 width800, height600, background_colorwhite, max_words100 ) wc.generate_from_frequencies(topic_words) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.savefig(output/topic0_wordcloud.png, dpi300)逻辑说明WordCloud 直接接收一个词频字典generate_from_frequencies 跳过默认的从文本分词步骤。font_path 指定中文字体文件kaiti.TTF 就在 data 目录里。如果不指定 font_pathmatplotlib 和 wordcloud 会用默认字体渲染中文结果满图都是方框。参数说明topn50 表示取主题权重最高的 50 个词做词云词太少图会单薄词太多会出现大量低频词干扰视觉。max_words100 控制词云中最多显示的词数实际词数会取 min(50, 100) 中的有效值。dpi300 是出图清晰度用于论文插入时建议 300屏幕展示 150 就够。4.3 数据从 CSV 换成自己的评论时的改动清单把这份代码改成自己的数据需要动的地方其实不超过 5 处。先把 CSV 路径换掉再确认列名。如果自己的评论是 Excel 文件就改成 pd.read_excel。其次是 person.txt 换成自己领域的专有名词比如分析《三体》就加“罗辑”“章北海”“黑暗森林”。停用词表也要按领域补充分析影评时“电影”“电视剧”这种词可以考虑加进去分析商品评论时“宝贝”“快递”要加。剩下词典过滤参数和主题数需要按新语料重新跑一遍前面第 3 章不要沿用旧的 k 值。我自己的习惯是全流程跑通第一遍时先用 100 条抽样数据验证代码通畅再全量跑。很多问题——比如分词报错、编码问题、内存溢出——在小样本上暴露得更快调试起来也不心疼时间。5. 避坑与排查豆瓣评论数据做 LDA 最常见的五个翻车现场5.1 词云图全是方框中文字体路径没生效现象词云图生成成功但图片里所有汉字都显示为空心方块。原因WordCloud 默认使用英文渲染引擎 DroidSansMono遇到中文无法渲染。环境中存在多个中文字体时font_path 参数如果写的是相对路径可能因为运行目录切换导致找不到文件。解决把 font_path 改成绝对路径或者在代码里用 os.path.join 拼接路径。最保险的方式是把 kaiti.TTF 复制到项目根目录然后写 font_pathkaiti.TTF。我跑的时候出现过一次“明明文件存在但报 FileNotFoundError”查了一下是脚本在 output 子目录执行相对路径自然就断了。5.2 训练报 TypeError传入的对象不是预期类型现象运行 LdaModel 时报错提示 corpus 不是预期格式或者 id2word 里出现不可哈希的对象。原因最常见的是 texts 里的元素没有分词直接把原始字符串传给了 doc2bow。doc2bow 期望接收 token 列表如果给的是字符串它会把字符串拆成单个字符导致词典全部是单字。另一种情况是 texts 中出现 NaNpandas 读 CSV 时空值默认是 NaN不处理会一路传递。解决训练前加一行过滤import math texts [t for t in texts if isinstance(t, list) and len(t) 0]同时在读 CSV 之后立即执行 df.dropna(subset[comment])把评论列为空的记录删掉。我处理的庆余年.csv 里就有几条纯空白评论不删的话 gensim 会报 ValueError。5.3 困惑度曲线震荡严重随机种子没有固定现象连续运行两次 perplexity.py画出的两条曲线形态差异很大k5 时有时困惑度 200有时 500。原因LdaModel 内部的 Gibbs 采样是随机过程没有指定 random_state 时每次运行的初始化状态都不同。小语料上这种随机性会被放大。解决在 LdaModel 构造参数里加 random_state42并把这个参数传给所有模型训练环节。如果用了 sklearn 的 LDA就设 random_state2024。固定之后曲线基本稳定微小的起伏是正常现象。5.4 主题词全是“我觉得”“真的”“一个”停用词表不够狠现象打印出来的 10 个主题里每个主题的前 5 个词都是“觉得”“真的”“虽然”“但是”这种词。原因stopwords.txt 覆盖不全。豆瓣长评论里“楼主”“评论”“感觉”“剧情”这类词出现频率极高但通用停用词表不含它们。解决先跑一次模型top 词里出现的高频噪音词收集起来追加到 stopwords.txt 里重新训练。这个过程通常要做 2~3 轮。我一般会用一个快速循环来检查from collections import Counter all_words [w for text in texts for w in text] word_freq Counter(all_words) # 打印出现次数最多且无实义的 50 个词 print([w for w, c in word_freq.most_common(50) if len(w) 1])把视觉上明显是噪音的词记录到 stopwords.txt每行一个词UTF-8 编码。5.5 doc_top_topic 输出结果和主题词对不上现象某条评论被分到 Topic 3但打印出来的 Topic 3 主题词和这条评论内容完全无关。原因gensim 的 LdaModel.get_document_topics 返回的是每条评论的主题概率分布但默认情况下返回的分布是归一化后的概率值受 alpha 先验影响。如果只取概率最大值作为归属主题对于短评论或主题模糊的评论这个归属几乎没有意义。解决取主题归属前先看概率分布是否集中。用代码检查topics lda.get_document_topics(corpus[0], minimum_probability0.1) print(topics)如果返回的多条主题概率都低于 0.3说明这条评论内容太杂或太短不必强求归属。实际分析时我通常只统计那些有明确主导主题概率大于 0.5的评论其余的标记为“混合评论”这样统计出的各主题占比才有说服力。6. 进阶验证用 pyLDAvis 做主题可交互分析顺便自查结果是否可信前面跑完你手里已经有了主题词列表、困惑度曲线、一致性曲线和词云图但这些都是静态结果。我做完这个项目之后每次都会补一步——用 pyLDAvis 把模型结果转成交互式可视化拖动滑块就能调整 λ 参数在“主题间距离”和“词频权重”两个维度之间切换。这一步不但能验证主题是否真的分得开还能直接输出成 html 放进毕设附录观感好很多。import os import pyLDAvis.gensim_models as gensimvis import pyLDAvis # 训练好的 lda 模型、corpus、dictionary 直接传入 vis_data gensimvis.prepare(lda, corpus, dictionary) # 保存成独立 html 文件 pyLDAvis.save_html(vis_data, output/lda_vis.html)逻辑说明prepare 函数接收训练好的 LDA 模型、词袋语料和词典内部计算各主题之间的 JS 散度距离和词频权重生成一个 Web 端交互图表。save_html 把它保存为单文件 html不需要启动服务器浏览器直接打开就能拖拽。参数说明prepare 的内部计算默认已经足够好不需要额外调参。但有一个前提条件——模型训练时 per_word_topics 设为 True否则部分可视化功能会受限。如果报错说缺少 topic-word 分布回到训练参数里补上即可。可视化界面出来后左侧是主题气泡图气泡大小代表主题在语料中的占比气泡距离代表主题之间的相似度。合理的模型应该看到气泡分布分散没有大面积重叠每个气泡内的 top 词有序排列前几个词能一眼概括主题。如果看到 10 个气泡挤在画面中央互相重叠说明 k 值偏大或者语料太杂需要回到第 3 章重新选 k。如果做出来效果正常最后给每条评论打上主题标签按主题分组摘录几条代表性评论就算完成了一个完整结论闭环。整套流程走到这里我从“拿到 zip 包”到“能对着论文讲出每个主题的语义解释”大概用了 4 个小时绝大多数时间花在停用词迭代和主题数确定上。我记得第一次跑通这个项目时困惑度曲线足足震荡了三轮才找到稳定的拐点最后才发现是 random_state 没有固定。从那以后我每次跑 LDA 都会先固定随机种子并把分词结果 dump 一份到本地文本缓存避免反复重跑分词浪费时间。希望这份拆解能让你绕过我踩过的这些坑快速把这套流程跑出自己的结果。本文还有配套的精品资源点击获取
返回列表