ARTICLE DETAIL

资讯详情

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

用LDA主题模型拆解电商评论:从特征提取到销量预测的Python实践

用LDA主题模型拆解电商评论:从特征提取到销量预测的Python实践 简介针对电商平台评论数据挖掘需求这份Python项目完整实现了基于LDA主题模型的情感分析流程适合作为Python课程设计或文本挖掘入门实践。项目以爬虫获取的评论数据为起点通过预处理脚本完成分句、jieba分词与词性标注、停用词过滤等操作再借助LDA主题模型提取评论特征名词并结合情感副词与情感词进行加权评分最终构建以特征名词为列的评论得分数据。随后基于PCA、皮尔逊相关系数等方法降维训练逻辑回归、SVM与Xgboost模型用于销量排名预测。压缩包共10个文件含4个CSV数据文件、3个Python脚本、2个pyc缓存文件及1个说明文档整体仅4.1MB结构清晰便于学习。目前已有1817人学习适合希望掌握中文文本预处理、主题建模与情感分析完整链路的学生和开发者参考。1. 用 LDA 主题模型拆解电商评论为什么先抽特征再打分比直接情感分析更靠谱做电商数据分析或者正在写 Python 课程设计的人大概率都对着几万条中文评论发过愁人工读不完程序算又不知道算什么。直接跑情感分析给整条评论一个正负分得到的是一个黑匣子——知道这条是好评却不知道它在夸屏幕还是骂续航更没法拿它预测销量排名。这份资源里的思路正好绕开这个坑先用 jieba 做分句、分词和词性标注再用 LDA 主题模型把评论里的产品特征名词抽出来给每个特征配上情感副词和情感词的加权得分把评论文本变成一个以特征词为列向量的评分矩阵最后交给 LR、SVM、XGBoost 去预测销量排名。适合正在做 Python 课程设计、毕业设计或者想把手头评论数据变成可解释商业结论的从业者。2. 数据预处理三步流水线分句、jieba 分词与词性标注的工程细节预处理决定 LDA 的上限。评论是噪声很大的短文本重复刷单、句末标点混乱、口语词密集直接拿原始文本跑主题模型出来的主题全是「不错」「还行」「东西」这类占位词。data_pre.py 做的事就是三步分句、分词加词性标注、去停用词。这三步做完每条评论从一段话变成一个干净的「带词性的词序列」。依赖环境只需要基础库jieba、pandas、numpy、re后面 LDA 会用到gensim建模用到scikit-learn。整个流程跑在 Python 3.6 及以上没有问题资源包里的__pycache__和.pyc文件说明作者当时用的 3.6 环境不影响你用自己的版本重跑。2.1 分句一条评论多个观点按句切分是第一步一条评论经常包含多个观点比如「屏幕很清晰但是续航太差了」。前一句在夸后一句在骂如果整体打一个分两个情感互相抵消特征也混在一起。按句末标点切分后每一句单独做特征提取和情感打分最后在累加时自然形成「屏幕 1、续航 -1」这种结构。import re import pandas as pd def load_comments(csv_paths): 读取多个评论 CSV统一列名后合并去重 frames [] for path in csv_paths: df pd.read_csv(path, encodingutf-8-sig) # 原始 CSV 里列名常见有 comment / content / review_text按需改 if comment not in df.columns: df df.rename(columns{content: comment}) frames.append(df[comment].dropna()) all_comments pd.concat(frames, ignore_indexTrue) # 电商刷单和复制评价会产生完全相同的文本先去重 all_comments all_comments.drop_duplicates() return all_comments.tolist() def split_sentences(text): 按句末标点切分过滤空段 parts re.split(r[。!?;], text) return [p.strip() for p in parts if p.strip()]utf-8-sig是为了兼容 Windows 记事本导出的带 BOM 头文件不指定时第一列列名会多一个隐藏字符\ufeff。drop_duplicates在评论量大的时候效果明显同一时间段的促销评论经常出现几十条一模一样的文本不剔除会让 LDA 里某个主题被刷单评论主导。切分正则里包含了中英文句末标点分句后每个句子独立成行后续打分就在句子级别做。2.2 jieba 分词与词性标注特征词看名词情感词看形容词中文分词不能按空格切必须借助分词工具。jieba 是这里最合适的选择一是轻量二是jieba.posseg同时输出词性和词分词和词性标注一步到位。产品特征词基本都是名词比如屏幕、续航、手感、物流情感词以形容词为主比如好、差、满意、垃圾。词性标注的价值就在这里后续可以用pos.startswith(n)直接筛出候选特征词而不是让 LDA 在动词和副词里找特征。import jieba import jieba.posseg as pseg def seg_with_pos(sentence): 分词并返回 (词, 词性) 列表保留形容词和副词单字 words pseg.cut(sentence) # 单字形容词/副词不能过滤比如 好、差、很 return [(w.word, w.flag) for w in words if (len(w.word.strip()) 1 or w.flag in (a, d)) and w.word not in 。、]pseg.cut返回的是生成器每个元素是pair有.word和.flag两个属性。这里对单字做了特判形容词和副词即使只有一个字也保留「好」「差」是整个情感打分体系里的高频词如果被长度过滤掉后面情感词典几乎失效。名词的单字词很少所以可以不保留。如果产品是手机、电脑这类领域词jieba 可能把「充电宝」切成「充电 / 宝」需要在分词前加载自定义词典常见做法是准备一个 user_dict.txt每行一个词加词性和词频用jieba.load_userdict加载。jieba 词性标注的常用标签就几个做特征筛选时只需要记住这些。标签含义在流程中的用途n名词LDA 特征词候选来源a形容词情感词候选来源d副词程度副词加权来源v动词一般不用少数特征词是动名兼类2.3 停用词过滤去掉助词和量词但千万别动否定词和程度副词分词之后文本里大量是「的」「了」「就」「一个」这类功能词它们在每个主题里都会出现是 LDA 主题词里最大的噪声源。停用词表一般用公开的哈工大停用词表或百度停用词表但直接套用会踩一个大坑很多通用停用词表把「不」「没」「没有」也当无意义词过滤了。这三个词是否定词直接影响情感极性判断必须保留。STOP_WORDS set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: STOP_WORDS.add(line.strip()) # 电商评论领域停用词高频但与产品特征无关 DOMAIN_STOP {东西, 产品, 商品, 一个, 这个, 那个, 感觉, 反正, 下来, 怎么, 什么} STOP_WORDS.update(DOMAIN_STOP) # 关键从停用词表里把否定词和程度副词摘出来 NEG_WORDS {不, 没, 没有, 别, 莫} STOP_WORDS.difference_update(NEG_WORDS) STOP_WORDS.difference_update({很, 太, 非常, 比较, 特别, 有点}) def filter_stop(words): 输入 (词, 词性) 列表输出过滤后的列表 return [(w, pos) for w, pos in words if w not in STOP_WORDS and not w.isdigit()]difference_update是这里的后悔药。通用停用词表里是否定词和程度副词的重灾区加载后强制移除。停用词处理原则可以整理成一张表后续调整词表时对照着检查。词类别是否过滤原因连词助词的、了、就、在过滤对主题和情感贡献为零否定词不、没、没有保留直接反转情感极性程度副词很、非常、太、有点保留决定情感强度加权打分要用电商领域高频词东西、商品过滤出现在所有评论里会污染 LDA 主题预处理跑完后每条评论变成类似[(屏幕, n), (清晰, a), (续航, n), (差, a)]的结构。这个结构是第 3 章 LDA 的直接输入也是第 4 章情感打分的基础。3. LDA 主题模型提取产品特征主题数选择、特征名词与情感加权打分LDA 在这个项目里的角色不是给人看主题而是从海量分词结果里定位「用户到底在讨论哪些产品特征」。拿到一个包含大量名词的分词语料后LDA 会把共现频率高的词聚成主题比如「像素、拍照、清晰、夜景」聚成一类「物流、发货、快递、包装」聚成一类。每个主题的 top 词就是产品特征词的候选集。3.1 语料向量化与 LDA 训练用 gensim 还是 sklearngensim和sklearn都有 LDA 实现这里选 gensim理由有三个一是Dictionary类对分词后的词列表支持好可以直接过滤低频和高频词二是CoherenceModel只在 gensim 里有主题数选择需要它三是 gensim 的 LDA 训练速度快几百 M 的评论语料也能在合理时间内跑完。sklearn 的LatentDirichletAllocation优势是和Pipeline配合方便但主题一致性评估要自己算不划算。from gensim.corpora import Dictionary from gensim.models import LdaModel # 输入分词后的句子列表每句一个 list元素是词不含词性 # 只保留名词和形容词产品特征基本是名词 word_lists [[w for w, pos in sent if pos.startswith(n) or pos.startswith(a)] for sent in seg_sentences] word_lists [[w for w in words if w not in STOP_WORDS and len(w) 1] for words in word_lists if len(words) 3] dictionary Dictionary(word_lists) # 过滤极端词至少出现3次且不在超过50%的句子里出现 dictionary.filter_extremes(no_below3, no_above0.5) corpus [dictionary.doc2bow(doc) for doc in word_lists]filter_extremes两个参数是 LDA 效果的关键。no_below3表示一个词至少在 3 个句子里出现过只有一两个句子里的孤立词对主题贡献为零no_above0.5表示一个词最多在 50% 的句子里出现像「不错」这种好评通用词如果没有被停用词表清干净这里还能兜底。过滤后如果词典规模低于 500说明语料太杂或者预处理太粗暴需要回头看停用词表。训练模型本身很简单参数才是重点。lda LdaModel(corpuscorpus, num_topics12, id2worddictionary, passes20, alphaauto, random_state42) for topic_id in range(lda.num_topics): print(topic_id, lda.show_topic(topic_id, topn12))num_topics就是主题数先拍一个 12 作为初始值下一节讲怎么正经地选它。passes20表示整个语料迭代 20 遍数值太小模型没收敛太大训练时间翻倍20 是一个平衡点。alphaauto让模型自动估计主题分布的稀疏程度电商评论这种短文本场景下自动估计比固定alpha效果好。random_state42必须固定否则每次训练结果都不一样后面的主题一致性对比就失去了参照物。3.2 主题数怎么定困惑度和一致性以一致性为准LDA 的主题数是个玄学但也不能完全拍脑袋。gensim 里有两个参考指标困惑度perplexity和主题一致性coherence。困惑度是模型内部的概率指标数值越低越好但它跟人类可解释性经常反向主题数越大困惑度越低结果是一个主题数大到离谱、每个主题都无法解释的模型。所以这个项目里用一致性它衡量主题内词的共现质量越高说明主题越凝聚。from gensim.models.coherencemodel import CoherenceModel candidates list(range(8, 21)) coh_scores [] for k in candidates: model LdaModel(corpuscorpus, num_topicsk, id2worddictionary, passes20, alphaauto, random_state42) cm CoherenceModel(modelmodel, textsword_lists, dictionarydictionary, coherencec_v) coh_scores.append(cm.get_coherence()) print(fk{k}: {coh_scores[-1]:.4f})coherencec_v是最常用的计算方式它结合了词的滑动窗口共现信息比u_mass更符合人对主题一致性的感知。选择策略不是机械地取最高点而是取「一致性较高区间里的最小值」。比如 k12 和 k18 得分都在 0.55 附近选 12因为主题数越少每个主题越有区分度后续人工核对特征词的成本越低。电商产品评论数据量在几万条时主题数落在 8 到 20 这个区间比较正常超过 20 会出现大量语义重复的主题。提示主题一致性曲线只是参考机器指标不会替你做业务判断。选完主题数后务必把每个主题的 top 12 词打印出来人工扫一遍确认确实对应到屏幕、续航、物流这类产品特征而不是「不错、还是、就是」这种占位词。3.3 特征名词抽取与情感打分特征词前后窗口里的加权得分主题模型输出的 top 词里混着一些噪声需要人工从每个主题里拣出真正的产品特征名词。以手机评论为例拣完大概就是屏幕、续航、充电、手感、质量、物流、价格、外观、拍照、音质。这些词构成FEATURE_WORDS列表是情感打分的落点。打分逻辑是对每句话做分词找到特征词出现的位置然后在它前后各取 3 个词作为窗口窗口里出现的形容词决定情感极性程度副词放大或缩小强度否定词反转极性。import jieba.posseg as pseg # 情感词典形容词 - 情感分正负号代表褒贬 SENTIMENT_DICT { 好: 1.0, 不错: 0.8, 满意: 0.9, 赞: 1.0, 差: -1.0, 垃圾: -1.2, 一般: -0.2, 失望: -0.9, } # 程度副词放大或缩小情感强度 DEGREE_DICT { 很: 1.5, 非常: 1.8, 太: 1.6, 特别: 1.7, 比较: 1.2, 有点: 0.6, 还: 0.8, } def score_sentence(sentence, feature_words): 给一句话打特征情感分返回 {特征词: 得分} tokens [(w, pos) for w, pos in pseg.cut(sentence) if w not in STOP_WORDS and len(w.strip()) 1] words [t[0] for t in tokens] scores {} for i, w in enumerate(words): if w not in feature_words: continue # 取特征词前后各3个词作为上下文窗口 window words[max(0, i - 3):i 4] total 0.0 for j, sw in enumerate(window): if sw not in SENTIMENT_DICT: continue degree 1.0 # 情感词前一个词是程度副词则放大权重 if j - 1 0 and window[j - 1] in DEGREE_DICT: degree DEGREE_DICT[window[j - 1]] # 情感词前一个词是否定词则极性反转 if j - 1 0 and window[j - 1] in NEG_WORDS: degree * -1 total SENTIMENT_DICT[sw] * degree scores[w] total return scores窗口大小前后各 3 是经验值覆盖了「屏幕非常清晰」「续航一点都不好」这类常见句式的信息范围窗口太小漏掉程度副词窗口太大把其他特征词的评价也算进来。否定词处理放在程度副词之后两者同时出现时先放大再反转比如「非常不好」非常给 1.8 倍然后否定反转成 -1.8。情感词典是这套打分体系里最需要扩展的部分。开箱即用的词典只有十几个词真实评论里情感表达千奇百怪常见做法是拿知网 HowNet 情感分析词典或者大连理工情感本体库做基础词表再把自己数据里高频出现的形容词追加进去。光靠基础词表跑出来的分数会很稀疏大量句子因为找不到情感词而得零分。这个打分方式本质上是一个加权求和精度跟深度学习模型没法比优势是可解释每一条分数都能回溯到对应的情感词和程度副词。4. 特征工程与建模基座从特征词得分 DataFrame 到 PCA 和皮尔逊筛选情感打分完成后数据从文本变成了结构化矩阵。这一步的产物直接决定建模效果每一行是一条评论或者一个产品每一列是一个产品特征值是情感加权分最后一列是销量排名。特征工程要做的是把这个矩阵整理干净剔除掉跟销量排名无关的列再用主成分分析处理高维共线问题。4.1 把每条评论的得分合并成以特征词为列向量的 DataFramescore_sentence 返回的是字典键是特征词值是得分。把所有句子的得分按评论累加就得到一条评论的特征向量。累加而不是取平均是因为同一特征被多次提及说明用户对这个特征关注度高在销量预测里有更强的信号。import pandas as pd rows [] for text in all_comments: row_scores {} for sent in split_sentences(text): sc score_sentence(sent, FEATURE_WORDS) for feat, val in sc.items(): row_scores[feat] row_scores.get(feat, 0) val if row_scores: rows.append(row_scores) df pd.DataFrame(rows).fillna(0.0) # 如果原始数据里有 product_id / product_name 列可以聚合到产品级 # df df.groupby(product_id).mean()fillna(0.0)很重要一条评论不会提到所有特征词没提到就是 0 分。这里不能填 NaN否则后面pearsonr和建模都会因为缺失值报错。如果要做产品级预测用groupby(product_id)聚合并取均值把同一产品所有评论的特征得分平均成一个向量如果没有 product_id就用评论级数据直接建模预测一条评论对应的销量倾向。聚合后的数据样例长这样评论屏幕续航充电价格物流销量排名评论11.8-1.00003评论2000.8-1.51.018评论30.6000074.2 皮尔逊相关系数先筛掉跟销量排名无关的特征特征词列可能有一二十列里面有些特征跟销量几乎没有关系。比如「包装」这个词在评论里经常出现但用户骂包装并不会明显影响销量排名。先用皮尔逊相关系数做一轮过滤把和销量排名相关性弱的特征剔除能降低模型过拟合风险也能减少后续 PCA 的维度。from scipy.stats import pearsonr target df[rank] feature_cols [c for c in df.columns if c ! rank] selected [] for col in feature_cols: r, p pearsonr(df[col], target) # 经验阈值|r| 0.1 且 p 0.05 if abs(r) 0.1 and p 0.05: selected.append(col) print(保留特征:, selected)皮尔逊相关系数只能捕捉线性关系这里用来做初筛是够的因为后续模型里还有 XGBoost 能捕捉非线性。abs(r) 0.1是一个经验阈值样本量小的时候可以放宽到 0.05样本量超过一万条时 0.1 以下的相关性虽然统计显著但实际预测价值很低。p 0.05确保这个相关性不是抽样噪声带来的。筛完后的特征列直接给 LR 和 SVM 用维度一般在 5 到 15 之间不会爆维度。4.3 PCA 降维特征多了才用别一上来就降维PCA 在这个项目里是备选方案不是必走流程。只有当特征数超过 50 个或者特征之间存在明显共线性时才需要降维。什么叫明显共线性比如同时保留了「屏幕」和「屏」两个特征它们高度正相关LR 的系数估计会不稳定。PCA 把原始特征线性组合成新的主成分彻底消除共线性。from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA X StandardScaler().fit_transform(df[selected]) pca PCA(n_components0.95) X_pca pca.fit_transform(X) print(pca.explained_variance_ratio_.cumsum())StandardScaler必须先做PCA 对特征的尺度敏感屏幕的得分范围可能是 -3 到 3价格的得分范围可能只有 -1 到 1不标准化的话价格特征被自动忽略。n_components0.95表示保留 95% 的方差贡献率系统自动决定保留几个主成分不用手动指定。跑完打印explained_variance_ratio_.cumsum()可以看到前几个主成分累计解释了多少方差。有一个必须提前想清楚的点PCA 之后的主成分是原始特征的线性组合完全失去可解释性。如果你后面要做特征归因——想知道到底哪个产品特征在带动销量——就不要走 PCA直接用皮尔逊筛选后的原始特征喂给 XGBoost看它的feature_importances_。PCA 只适合纯预测任务不适合需要解释的场景。5. 避坑排查电商评论情感分析里最容易翻车的五个细节这套流程我拆过好几遍踩过的坑集中在三个层面分词和词性标注、LDA 训练、建模评估。每个坑都出现过「跑完一遍全流程才发现结果不可用」的情况返工成本很高。下面按环节整理了五条最典型的坑每条按照现象、原因、解决写清楚。坑位涉及环节一句话建议坑1词性标注领域词不在 jieba 词典里加载自定义词典坑2停用词表否定词被过滤情感极性直接翻转坑3主题数用一致性选主题数不能拍脑袋坑4语料质量短句太多会让 LDA 学不到词共现坑5建模评估销量排名当回归做误差评估失真5.1 分词与词性标注的坑坑1领域词的词性被标错特征名词在筛选时消失。现象是「充电」被pseg标成动词 v「手感」被标成动词而 LDA 特征筛选条件用的是pos.startswith(n)这批真实的特征词直接被过滤掉了。原因是 jieba 默认词性由通用词典决定电商领域词汇不在其中。解决方法是先跑一遍全量分词把高频的领域词收集到一个 user_dict.txt每行格式是「词 词性 词频」再用jieba.load_userdict(user_dict.txt)加载。个别词还是不对的话用jieba.suggest_freq(充电, tuneTrue)调整切分权重。坑2停用词表里带否定词情感分极性和真实评价完全相反。现象是「屏幕不太好」经过停用词过滤后变成「屏幕太好」打分算出 1.6 分。原因是从网上下载的通用停用词表把「不」「没」「没有」列进了无意义词。解决方法是加载停用词后强制用STOP_WORDS.difference_update(NEG_WORDS)把否定词和程度副词摘出去这条逻辑我在第 2.3 节里作为强制操作写进了代码不是可选项。每次换新的停用词表都要重复这一步。5.2 LDA 训练与特征提取的坑坑3主题数拍脑袋主题词全是「不错、还行、东西」。现象是打印每个主题的 top 10 词发现一堆高频通用词没有一个跟产品特征相关。原因有两层停用词表不干净让通用词在各个主题里都有高概率主题数设置得太少模型被迫把多个特征主题合并成一个混合主题。解决方法是先用第 3.2 节的一致性曲线跑一遍确定候选区间然后把每个主题 top 词里的功能词追加进停用词表重新跑一遍预处理和 LDA。这个「清一次停用词、重跑一次」的循环通常要两三轮才能稳定。坑4短句太多LDA 把噪声当主题。现象是分词后大量句子只有一两个词比如「很好」「差评」LDA 学不到词之间的共现关系主题分布趋近于均匀分布。原因是分句时把「很好。」这种独立短句也切了出来。解决方法是预处理时把词数少于 3 的句子丢弃或者把同一个评论内的连续短句合并成一句话再喂给 LDA。代价是语料量变小但主题质量明显提升这个取舍是划算的。5.3 建模评估的坑坑5销量排名当连续回归目标MSE 一直降不下来。现象是模型预测排名 45真实排名 10MSE 巨大且怎么调参都无济于事。原因是排名不是连续等距变量第 1 名到第 2 名的差距和第 50 名到第 51 名完全不同回归模型假设误差服从高斯分布这个假设在排名数据上不成立。解决方法是把任务转成二分类用销量排名的中位数切分排名前 50% 记为 1后 50% 记为 0分类问题的评估指标用 F1如果必须保留回归评估指标换成 Spearman 等级相关系数只关注预测和真实的排序一致性不看绝对差距。6. 建模前的对比验证LR、SVM、XGBoost 的交叉验证与一个验证习惯特征工程做完进入 model.py 里的三模型对比环节。第一步不是调参是先用默认参数把三个模型的 baseline 跑出来。只有等 baseline 之间有可比性了再谈优化。销量排名二分类可以避免第 5 章坑 5 里的回归失真问题划分方式用排名中位数。from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from xgboost import XGBClassifier from sklearn.model_selection import cross_val_score import numpy as np X df[selected].values # 4.2 皮尔逊筛选后的原始特征 y (df[rank] df[rank].median()).astype(int) models { LR: LogisticRegression(max_iter1000), SVM: SVC(kernelrbf, C1.0), XGB: XGBClassifier(n_estimators200, max_depth4, learning_rate0.1, random_state42), } for name, model in models.items(): scores cross_val_score(model, X, y, cv5, scoringf1, n_jobs-1) print(f{name}: {scores.mean():.3f} ± {scores.std():.3f})三个模型都跑五折交叉验证scoringf1是因为排名二分类的两个类别不完全均衡用准确率会虚高。LR 的max_iter1000是为了防止默认迭代次数不足导致不收敛警告SVM 用 rbf 核处理特征和排名的非线性关系XGB 固定random_state42保证每次跑出来的结果可复现。跑完 baseline 后还有一步值得做用 XGBoost 的feature_importances_反推哪些产品特征在真正带动销量xgb models[XGB].fit(X, y) importance sorted(zip(selected, xgb.feature_importances_), keylambda t: t[1], reverseTrue) for feat, imp in importance[:5]: print(feat, round(imp, 4))这一步能回答业务问题屏幕分和续航分哪个对销量排名影响更大。前提是特征没有经过 PCA 降维所以这个操作要放在第 4.3 节之前决定——降维和特征归因二选一不能都要。我对这种多模型对比有一个习惯性动作也算是个强制性检查先把cross_val_score的cv5换成StratifiedKFold(n_splits5, shuffleTrue, random_state42)因为销量排名二分类里好评产品往往占比小普通 K 折可能把少数类样本全分到某一折里导致 F1 剧烈波动。每次跑三模型对比前还会取 100 条评论走一遍「预处理 → LDA → 打分 → 建模」的最小链路确认编码、分词、主题质量都没问题再上全量这一步能省掉大量因为 CSV 编码或者停用词表错误导致的深夜返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表