ARTICLE DETAIL

资讯详情

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

电商评论情感分析:LDA主题模型完整流程与实战

电商评论情感分析:LDA主题模型完整流程与实战 简介一套基于LDA主题模型的电商产品评论情感分析完整项目面向高校计算机相关专业学生尤其适合需要完成课程设计、期末大作业或毕业设计的初学者也适合希望学习主题建模与情感分析结合应用的Python开发者。项目包含Python源码与详细文档说明代码注释充分部署简单即可运行能够完整呈现从评论数据清洗、分词处理、LDA主题提取到情感极性判别的全流程。资源包共含1380个文件以478个Python脚本为核心辅以237个CSV评论数据集、178个TXT说明、125个HTML可视化页面以及一批依赖库与配置文件压缩包整体53.97MB。目前已有344人学习下载。内容除源码外还提供爬取的多批次电商评论数据如美的2019/2020年评论、正面情感标注CSV、主题模型训练产物与ipynb示例便于直接替换数据复用结合详细注释和文档可快速掌握各模块的接口逻辑二次开发或用于答辩展示均非常方便是高分课程项目的有力参考。1. 电商评论的情感分析为什么离不开 LDA 主题模型只给商品打个平均分是电商评论分析里亏得最狠的做法。98% 的好评率面前差评区里“尺码偏小”“物流太慢”“客服已读不回”这些真实痛点被一个平均数全盖住了。LDA 主题模型擅长把一堆评论按隐含主题拆开情感分析负责判断每句话的好坏倾向两者一组合就能回答“用户到底在夸什么、骂什么”。这篇文章从数据处理讲起把清洗、分词、情感打分、LDA 训练到主题×情感交叉结论的完整流程跑了一遍适合毕业设计选型也适合想给运营拉一张问题清单的数据分析。2. 从评论表到带情感标签的语料清洗、分词与词典打分先明确一个顺序问题不要在原始评论上直接跑 LDA。原始文本里有 HTML 标签、重复刷单文案、十几个字的短评这些东西会直接污染主题词。我一般把流程分成两段先清洗分词得到 token 列表再用情感词典给每条评论打标签最后才把 token 喂给 gensim。这个顺序固定下来后面调参数时不会来回返工。2.1 评论数据常见字段与清洗顺序去重、去广告、去短评电商评论表长什么样不同平台差异不小但核心字段差不多user_id、product_id、rating、comment、create_time。拿到手之后别急着分词先做四步清洗去空、去重、去短评、去明显广告。顺序不要颠倒先 drop_duplicates 再去空能把重复的代价省下来。import re import pandas as pd df pd.read_csv(comments.csv, encodingutf-8-sig) df[comment] df[comment].astype(str) # 清洗四步空值 - 完全重复 - 短评 - 广告/刷单文案 df df[df[comment].str.strip() ! ] df df.drop_duplicates(subset[comment]) df df[df[comment].str.len() 4] # 刷单文案有固定套路用浅正则先滤一轮 ad_pattern r好评返现|加微信|联系客服|内部优惠 df df[~df[comment].str.contains(ad_pattern, naFalse)] print(f清洗后剩余评论条数: {len(df)})这段代码做了什么按字符串长度过滤掉 4 个字以下的短评因为“不错”“还行”这类评论分词后只有一两个词既撑不起情感分数也撑不起 LDA 的主题统计。广告正则只做第一轮过滤实际项目里我会再叠加一条规则把同一用户短时间内对同一商品连续打五星的评论标记出来作为“疑似刷单”单独存放不直接删。原因很简单刷单评论对 LDA 主题分布影响很大重复文案会让某些主题词异常集中。2.2 jieba 分词与停用词表把“的东西”这类噪声先拿掉中文评论分词基本默认 jieba原因是它不用训练装完就能跑自定义词典的接入成本也低。电商场景里真正的分词难点不在常用词而在“掉漆”“起球”“偏小”这些商品相关词。jieba 默认词库对这类词经常切错比如把“起球”切成“起”和“球”主题词出来就完全没法看。解决方法是维护一个 user_dict.txt一行一个词格式是“词 词频 词性”词频和词性可以省略。import jieba # 自定义词典路径按实际项目调整 jieba.load_userdict(user_dict.txt) stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) def tokenize(text): # lcut 返回 list比 cut 的生成器更好调试 words jieba.lcut(text) result [] for w in words: w w.strip() if not w: continue if w in stopwords: continue if len(w) 2: continue if w.isdigit(): continue result.append(w) return result df[tokens] df[comment].apply(tokenize)分词这块有个容易被忽略的点jieba 的 load_userdict 是可以多次调用的不用把词一次性凑齐。我先放一批商品属性词跑第一轮主题看到明显的切片错误再补第二轮。注意用户自定义词典并不会覆盖 jieba 内置词的切分优先级它只是把新词变成可识别的前缀组合。如果有词怎么切都不对用 jieba.suggest_freq(起球, True) 调整单个词频这条命令比反复改词典文件更直接。停用词表我一般用公共词表打底再往里追加电商评论里高频但无意义的口语词。最常见的坑是只清洗了“的、了、是”没处理“感觉、东西、真的、就是”。这批词在LDA主题词里出现频率极高会把真实主题词挤掉后面会专门讲这个问题。2.3 情感词典打分与阈值切分给每条评论贴上 pos/neg/neu 标签情感分析可选方案很多深度学习或者现成库都能做但这类项目我优先用情感词典打分。原因有两个一是电商评论数据通常没有人工标注监督学习的标签你得先花一天去标二是词典法结果可直接解释后期往文档里写“这条评论是负面因为包含‘破损’且没有否定词”时逻辑清楚。国产公开的情感词典里常用的像知网 HowNet 中文情感词典、BosonNLP 情感词典格式大多是正负词各一个 txt下载后直接读成集合就行。# 读入正负情感词典 POS_WORDS set(open(pos_words.txt, encodingutf-8).read().splitlines()) NEG_WORDS set(open(neg_words.txt, encodingutf-8).read().splitlines()) DEGREE_WORDS {非常: 1.8, 很: 1.5, 太: 1.5, 挺: 1.2, 有点: 0.7, 稍微: 0.6, 比较: 1.0} NOT_WORDS (不, 没, 没有, 别, 无, 不太) def sentiment_score(tokens): score 0 polarity 1.0 # 当前极性遇到否定词翻转 degree 1.0 # 程度副词倍率 for w in tokens: if w in NOT_WORDS: polarity -1.0 elif w in DEGREE_WORDS: degree DEGREE_WORDS[w] elif w in POS_WORDS: score degree * polarity polarity, degree 1.0, 1.0 elif w in NEG_WORDS: score - degree * polarity polarity, degree 1.0, 1.0 return score df[score] df[tokens].apply(sentiment_score) # 三档切分负、中性、正 df[label] pd.cut(df[score], bins[-float(inf), -0.5, 0.5, float(inf)], labels[neg, neu, pos])打分逻辑里最容易出错的是否定词和程度副词的叠加顺序。这段代码用 polarity 记录否定状态遇到“不满意”时“不”把 polarity 翻成 -1随后“满意”命中正词贡献的分数是 1 × (-1)最终为负逻辑成立。但如果评论是“不太满意”“不”和“太”放在相邻位置实际效果会被简化成一次翻转不会精确表达“程度减弱但仍是负面”。对短评来说这个误差可以接受真遇到“不是特别满意但也还行”这种话词典法基本算不准我后面直接把它归到 neu 档不去硬解。阈值为什么设在 ±0.5因为单条短评的情感得分通常集中在 -3 到 3 之间±0.5 能过滤掉那种偶发一个正词、但整体态度模糊的评论。不同数据的分数分布不一样我拿到 score 列后会先画个直方图如果分布整体偏移就把阈值改成打分结果的 30% 分位数和 70% 分位数而不是死守 0.5。3. 用 gensim 把 LDA 主题模型跑通主题数与参数调整LDA 的实现方案里gensim 的 LdaModel 是 Python 生态里最省事的。它封装了变分推断不需要自己写吉布斯采样训练、推断、保存模型都有现成接口。但这不代表参数可以乱填尤其是主题数 K直接决定后面交叉表能不能读。3.1 语料怎么喂给 LDA字典、词袋与最小训练流程gensim 的 LDA 不吃文本吃的是词袋向量。每条评论先经过上一步的 tokenize再通过 Dictionary 映射成整数 id然后 doc2bow 转成稀疏向量。这里有一个关键参数是字典的 filter_extremes它控制哪些词能进主题模型。no_below 表示词频低于该值的词删掉no_above 表示该词在语料中的文档占比高于该值就删掉。电商评论里“满意”如果出现在 70% 的文档里它对主题区分没有贡献直接删。from gensim import corpora, models # 构建字典并过滤极端词频 dictionary corpora.Dictionary(df[tokens]) dictionary.filter_extremes(no_below5, no_above0.5) # 每条 token 列表转成 bag-of-words 稀疏向量 corpus [dictionary.doc2bow(tokens) for tokens in df[tokens]] lda models.LdaModel( corpuscorpus, num_topics8, id2worddictionary, passes20, alphaauto, etaauto, random_state42, )这段代码里最值得调的是三个参数。alpha 控制文档-主题分布的稀疏性默认 auto 会让模型自己学但如果你的评论普遍很短alpha 倾向于把每条评论均匀分配到多个主题上主题就不清不楚。我遇到短文本会手动设 alphaasymmetric让模型更偏向少量主题主导。eta 控制主题-词分布一般保持 auto 或直接默认除非你想让某个主题强制偏向特定词。passes 是完整语料的遍历次数不是迭代次数。passes20 对小语料够用语料超过五万条时我习惯降到 10 到 15因为遍历成本高收益递减。chunksize 默认 2000它决定每轮处理的文档数内存压力大就调小比如 1000。random_state 这里必须固定否则同一次分析跑两次主题编号和词分布可能对不上后面交叉表就乱了。3.2 主题数 K 的判定困惑度是玄学一致性分数才是参考主题数选多少是 LDA 项目里最大的主观项。网上讲困惑度的多但困惑度在实际使用中会持续下降尤其在语料不规范时它根本给不出一个明显的拐点。我现在的习惯是不看困惑度用一致性分数。coherence 衡量主题词之间的语义相关程度c_v 分数越高说明主题越好读。from gensim.models.coherencemodel import CoherenceModel coherence_scores [] k_range range(4, 13) for k in k_range: lda_k models.LdaModel( corpuscorpus, num_topicsk, id2worddictionary, passes15, random_state42, alphaasymmetric, ) cm CoherenceModel( modellda_k, textsdf[tokens], dictionarydictionary, coherencec_v ) coherence_scores.append((k, cm.get_coherence())) print(fK{k}, c_v{coherence_scores[-1][1]:.4f})这段代码不是一个精确的自动选 K 方案它给你的是一条趋势线。我一般这样读如果 c_v 在 K8 之后不再明显上升甚至开始波动就选 8如果持续单调上升到 12 都没有平台期说明语料复杂度高主题拆得还不够细。另外主题的可解释性比数字重要。K 选 6 但每个主题词都能对上一类产品问题比 K10 但有两个主题重复讲同一件事强。要注意 c_v 计算的耗时。texts 传的是原始分词列表不是词袋向量CoherenceModel 内部会重新统计共现矩阵。语料超过十万条这个循环可能要跑十几分钟正常不用怀疑是死循环。3.3 从主题词反推业务含义给每个主题起个看得懂的名字模型训练完直接 print_topics 看结果是很多人迈不过去的一步。输出是一行行数字和词概率不能直接扔进文档。我的做法是把每个主题的关键词列成一张小表对着电商业务给主题起名字然后维护一个映射字典供后面交叉表使用。# 输出每个主题的 top 15 关键词 for topic_id, terms in lda.print_topics(num_topics8, num_words15): print(fTopic {topic_id}: {terms}) # 给主题命名映射关系来自人工解读 topic_names { 0: 物流时效, 1: 尺码偏差, 2: 客服态度, 3: 材质做工, 4: 价格优惠, 5: 包装运输, 6: 商品功能, 7: 售后服务, }这一步没有标准答案。一个主题是否能命名标准是它的 top 词里有没有一组同语义名词出现。比如“物流、快递、送达、慢”集中在一个主题里它讲的就是物流但如果“物流”和“掉漆”同时出现在 top 词里说明主题拆得太粗糙你得回头改 K 或者加强停用词过滤。给主题命名时不要用商品名用业务维度名方便后面不同品类的评论复用同一套分析框架。4. 主题×情感交叉分析把模型输出拼成产品改进结论到了这一步情感分也有了主题模型也有了剩下的是把它们合成结论。纯情感分析告诉你“差评率是 12%”纯 LDA 告诉你“有一类主题围绕尺码和退换”合在一起才能告诉你“尺码这类主题里负面占比 60%”。这个章节讲两种组合方式按情感分档分别训练 LDA以及一次训练后的交叉矩阵。4.1 按情感分档训练 LDA好评和差评分别说哪几件事最稳妥的做法是把正负评论拆开各训一个 LDA。原因很直接好评和差评关注的点经常完全错开。好评里用户讨论“效果”“价格”“信赖”差评里讨论“破损”“客服”“退货”如果混在一起主题会被高频词拉向中间两边特色都丢。分开训之后好评主题讲的是卖点差评主题讲的是痛点天然对应文档的“优势总结”和“改进项”两个章节。pos_tokens df.loc[df[label] pos, tokens].tolist() neg_tokens df.loc[df[label] neg, tokens].tolist() # 正负语料各自建字典注意 no_below 要调小因为数据减半 pos_dict corpora.Dictionary(pos_tokens) pos_dict.filter_extremes(no_below3, no_above0.5) pos_corpus [pos_dict.doc2bow(t) for t in pos_tokens] pos_lda models.LdaModel( corpuspos_corpus, num_topics5, id2wordpos_dict, passes20, random_state42, )分开训练的第一个坑是数据量。切分后正负各只有原来一半原来的 no_below5 会让大量低频词直接消失所以我改成 no_below3。第二个坑是主题数正负语料的主题数不要设成一样好评可能 3 个主题就够差评可能要 7 个这个由各跑一轮一致性分数决定。不要为了对称好看硬设成同一个值。4.2 交叉矩阵与单条评论主题归属从文档-主题分布说起如果不想分开训练也可以跑一个总 LDA然后用 get_document_topics 拿到每条评论的主题分布概率按情感标签汇总成交叉矩阵。我两种都用分开训练用于写报告交叉矩阵用于验证主题和情感的整体关联度。import numpy as np K 8 cross pd.DataFrame( np.zeros((K, 3)), index[fTopic{i} for i in range(K)], columns[pos, neu, neg] ) for tokens, label in zip(df[tokens], df[label]): bow dictionary.doc2bow(tokens) topic_dist lda.get_document_topics(bow, minimum_probability0.01) for topic_id, prob in topic_dist: cross.loc[fTopic{topic_id}, label] prob # 按行归一化看每个主题内的情感占比 cross cross.div(cross.sum(axis1), axis0) print(cross.round(3))minimum_probability 这个参数容易忽略。gensim 默认只返回概率大于 0.01 的主题如果你发现交叉矩阵很多行加总不是 1.0就是这个参数在生产。把它降到 0.01 再跑一遍让最弱关联也能计入统计。交叉表读法有一个要点不要看绝对值看行内占比。比如 Topic1 的 pos 列占 0.72说明这个主题主要由好评撑起来它是产品卖点如果某个主题 neg 占比超过 0.5它就是要写进改进项的重点区域。4.3 结论落地模板卖点、痛点、改进项怎么写进文档交叉表算完之后要转成业务语言。我通常建一张三列表格主题、情感构成、业务结论。表格能让评审五分钟看懂模型到底产出了什么。做个样例主题情感构成行归一化业务结论尺码偏差neg 0.61 / neu 0.22尺码偏小是主要负面来源需复核版型数据物流时效neg 0.47 / neu 0.31物流慢引发负面但中性占比高有提升空间商品功能pos 0.78 / neg 0.09核心功能口碑稳定可作为卖点主推写结论时注意一点交叉矩阵证明的是相关性不是因果。某个主题 neg 占比高只能说这个主题下的评论大多是负面的不能说“改好尺码表就一定能提升好评率”。文档里我会加一句“建议结合抽检评论原文验证”这句话能避免你的结论被运营挑战。另外单个主题内部如果同时出现“尺码偏小”和“尺码偏大”说明样本混杂了多个款型最好按商品 SKU 拆分后再交叉一次这部分可以在文档的“后续工作”里交代。5. 电商评论 LDA 情感分析的避坑指南网上关于 LDA 的教程不少但真正落地时翻车的地方往往不在模型本身而在数据和工程细节。这一章写几个高频坑都是我实际跑过的血泪经验按“现象、原因、解决”三段写。5.1 分词翻车领域词典没挂上主题词全是“这个那个”现象print_topics 出来的主题词是“这个、那个、还是、怎么”这种没有业务含义的词主题之间几乎看不出差异。原因jieba 的默认词库是通用领域电商商品词和评价口语词的覆盖率不够。比如“起球”两个字的词频低jieba 会切成两个单字单字在分词阶段又被我过滤掉了等于这个信息直接丢失。类似的情况还有“不掉色”“没缩水”。解决建立项目级用户词典把商品名、属性词、高频评价词都放进去。我一般分三类维护产品部件词屏幕、电池、手柄、属性词防水、静音、便携、电商特有词掉漆、起球、漏发。词典加载后要重启内核再验证直接在同一条代码里连续执行 load_userdict 再分词可能不生效因为 jieba 缓存了上一次的词典状态。验证方法是分词一条已知难例比如“这件衣服起球”检查是否被切成了“起球”。5.2 停用词表太薄主题里全是“东西感觉真的”这类废词现象主题的 top 词里高频出现“东西、感觉、真的、就是、还是”真正的卖点词被挤到 top 10 之后。主题命名没法做因为每个主题都长一样。原因公共停用词表覆盖的是书面语“东西、感觉”这类评论口语不在表里。它们在全语料里出现频率极高协方差主要被它们占掉主题就会被这些词主导。解决停用词表不要只建一遍。第一轮跑完 LDA把出现频率高的语气副词和无义名词追加到 stopwords.txt再重新训练。一般迭代两三轮主题词会从“东西感觉真的”收敛到“材质做工颜色尺寸”这类可解读词。注意不要误删业务词比如“感觉”在某些品类可能关联主观体验评论删之前先看原文。5.3 LDA 结果不稳定随机种子、passes 与主题漂移现象同样的语料跑两次训练两次的主题词排序差别很大文档里写的结论下次运行就对不上了。原因LdaModel 用的是在线变分推断初始化和抽样都有随机性。random_state 不固定每次训练初始化不同变分推断可能收敛到不同的局部最优解这不算 bug是算法的固有属性。解决训练时固定 random_state42并适当调大 passes。但固定种子只能保证你本地可复现不能证明结果稳定。我验证稳定性的方式是跑三个不同种子对比每个主题的 top 词重叠程度。重叠超过一半说明主题稳定可以放心用重叠很低就回到数据层面找问题通常是语料不干净或主题数选得不对。文档里写明“本报告基于 random_state42 训练结果”算是给这份分析一个版本号后续重新跑也不会引起结论混乱。5.4 评论太短导致主题稀疏合并文本与语料下限现象LDA 训练完成后有些主题只有一两个词撑场主题词之间没关联print_topics 输出像随机词拼接。原因电商评论短文本居多一条评论平均不到十个词而 LDA 本质是依赖词共现的概率模型。文档太短每个文档中词的共现次数太少主题学不到稳定结构。这是短文本主题建模的突出问题不是 gensim 能靠调参解决的。解决两个常用手段按需组合。第一种是合并同一用户、同一商品的评论到一条伪文档增加单篇文档长度第二种是调低 no_below 到 2 或 3让低频词有机会参与共现统计。合并要注意一个副作用主题粒度会变粗原本“物流慢”和“快递破损”两个独立主题可能被并到一起。我在项目里只在语料平均词数低于 6 时才做合并并会在文档里记录这个操作避免分析结果被误读为原始评论逐条统计。6. 验证与进阶一致性分数、pyLDAvis 可视化和后续扩展验证阶段我给自己定了一个可量化的门槛c_v 一致性分数低于 0.5 的主题不直接进入业务结论。这个 0.5 是经验值语料干净、主题清晰时常见在 0.5 到 0.7 区间如果只有 0.3 上下说明主题内部词义不相关你要回退到停用词和分词这一步重做而不是调 K 死磕。另一个验证方式是人工抽检每个主题抽 20 条原始评论人工判断它们是否归属该主题命中率超过七成才算模型对业务有效。可视化这步我习惯用 pyLDAvis 生成一个交互 HTML给运营看比贴 top 词列表直观得多。gensim 4.x 版本要导入 pyLDAvis.gensim_models不是旧的 pyLDAvis.gensim这个路径问题很容易让第一次用的人卡住。import pyLDAvis.gensim_models as vis vis_prepared vis.prepare(lda, corpus, dictionary) vis.save_html(vis_prepared, lda_vis.html)生成的 HTML 里左侧每个气泡是一个主题气泡大小代表主题在整个语料中的占比右侧是主题词的频率和相关性分布。判断主题质量的关键点在气泡之间是否重叠气泡基本不重叠说明主题区分度高好几个气泡叠在一团说明主题互相纠缠你要去检查语料或者减少主题数。打开文件后建议点击气泡逐个查看 top 词再把“右侧相关词列表”和 print_topics 的输出对照。再往后项目还可以往两个方向扩展。一是把 LDA 的输出当成弱标签喂给后续监督学习模型做情感分类这样能省掉人工标注成本二是文本上的情感分析稳定后如果数据源扩展到图文评论可以考虑多模态情感分析方向但那是另一个课题LDA 在这里可以退位为基线模型。就这个项目本身而言做到主题×情感交叉表并能给运营拉出问题清单已经完成了核心价值。最后说一个我的习惯每跑完一轮 LDA我会把主题词、一致性分数、random_state 记在项目 notes 里周三和周一跑出来的结果对不上时能快速定位是语料变了还是参数变了。数据分析和工程实现一样版本管理永远是后悔药的来源。希望这篇笔记能帮你把这个项目一次跑通。本文还有配套的精品资源点击获取
返回列表