☰
基于LDA与情感加权的电商评论分析及销量预测
2026/10/3 14:13:11 网站建设 项目流程

简介:这份资源面向具备一定Python基础、希望完成课程设计或入门文本挖掘的学习者,围绕电商产品评论的情感分析展开完整实践。内容以LDA主题模型为核心,串联数据预处理、特征提取与销量排名预测等环节:先通过jieba完成分句、分词与词性标注,再过滤停用词和标点,随后用LDA提取评论中的产品特征名词,并对特征词前后的情感副词、情感词加权打分,构建以特征名词为列向量的评论得分DataFrame,最后借助PCA、皮尔逊相关分析降维筛选特征,使用LR、SVM、Xgboost等模型训练并预测销量排名。资源包共10个文件,包含4个csv评论数据、3个py脚本、2个pyc缓存与1个md说明文档,压缩包约4.1MB,脚本与数据分离,便于直接运行和二次修改。目前已有1817人学习下载,适合作为文本挖掘、情感分析与机器学习建模的练手项目参考。

1. 电商评论情感分析:从 LDA 主题模型到销量排名预测的完整链路

电商运营每天面对成千上万条评论,靠人工逐条看根本不现实。更麻烦的是,评论里既有“物流快”“包装好”这种通用评价,也有“屏幕色彩偏冷”“续航比预期短两小时”这种针对具体产品特征的反馈。如果只做一个整体的好评差评二分类,等于把最有价值的信息扔掉了。这个 Python 项目做的事情,是把评论拆到“特征词 + 情感词”的粒度,用 LDA 主题模型提取产品特征,再对每个特征做情感加权打分,最后用 LR、SVM、XGBoost 三个模型去预测销量排名。它适合正在做课程设计、数据挖掘练手项目,或者想搭一套评论分析流水线的从业者。整个包结构不复杂:lda.py负责主题建模,data_pre.py做预处理,model.py跑分类模型,data目录下放了coments1.csv到coments4.csv四份评论数据。下面按“预处理 → 主题提取 → 情感打分 → 建模预测”的顺序拆开讲。

2. 数据预处理:分句、分词与停用词过滤的工程化写法

2.1 为什么预处理决定了后续 LDA 的上限

LDA 本质上是在词袋模型上做概率生成,它假设文档由若干主题混合而成,每个主题又是词上的概率分布。这个假设有个硬前提:输入的词必须干净、有区分度。如果停用词没去干净,“的”“了”“还”“就”这些高频虚词会占据主题词的头部位置,导致每个主题看起来都差不多,主题区分度直接崩掉。另一个容易被忽略的点是分句。一条评论可能同时包含“外观好看”和“续航太差”两个完全相反的评价,如果整条评论作为一个文档送进 LDA,主题分布会被平均掉,特征词和情感词的对应关系就断了。所以这个项目在预处理阶段做了三件事:分句、分词加词性标注、停用词过滤。常见做法是用 jieba 做分词和词性标注,用标点符号和换行符做分句边界。

2.2 分句与分词的代码实现

import jieba import jieba.posseg as pseg import re def split_sentences(text): """按中文标点分句,保留语义完整性""" # 常见分句符:句号、感叹号、问号、分号、换行 pattern = r'[。!?;\n]+' sentences = re.split(pattern, text) # 去掉空串和纯空白 return [s.strip() for s in sentences if s.strip()] def tokenize_with_pos(sentence): """分词并保留词性,便于后续筛选名词和形容词""" words = pseg.cut(sentence) result = [] for word, flag in words: # 只保留名词(n)、形容词(a)、动词(v)等有意义的词性 if flag.startswith(('n', 'a', 'v')) and len(word) > 1: result.append((word, flag)) return result # 示例 raw = "外观很漂亮,屏幕色彩鲜艳。但是续航太差了,用半天就没电。" for sent in split_sentences(raw): print(sent, tokenize_with_pos(sent))

split_sentences用正则把长评论切成短句,tokenize_with_pos借助 jieba 的词性标注接口,只保留名词、形容词、动词,并且过滤掉单字词。单字词在中文里往往是虚词或量词,保留下来对主题建模没有正面作用。词性标注这一步是为后面提取“特征名词”和“情感词”做铺垫——特征词基本落在名词上,情感词基本落在形容词和部分动词上。

2.3 停用词表的构建与过滤策略

def load_stopwords(path='stopwords.txt'): """加载停用词表,一行一个词""" with open(path, 'r', encoding='utf-8') as f: return set(line.strip() for line in f if line.strip()) def filter_stopwords(tokens, stopwords): """过滤停用词和标点""" return [w for w, flag in tokens if w not in stopwords and not re.match(r'^[\W_]+$', w)] # 常见停用词补充(项目里如果没有自带表,可以先用这个兜底) default_stopwords = set([ '的', '了', '还', '就', '都', '而', '及', '与', '着', '或', '一个', '没有', '我们', '你们', '他们', '这个', '那个', '什么', '可以', '因为', '所以', '但是', '而且', '虽然', '如果', '只是' ])

停用词表的质量直接影响 LDA 主题的可解释性。我一般会先跑一遍词频统计,把 Top 50 高频词里明显无意义的词手动补进停用词表,再重新跑。这个项目里data_pre.py应该已经内置了停用词处理逻辑,但如果你拿到的版本停用词表不全,按上面这个思路补一轮,效果会明显不一样。注意filter_stopwords里同时过滤了非文字字符,避免标点混入词袋。

3. LDA 主题建模:从评论语料中提取产品特征词

3.1 LDA 在评论分析里的角色定位

LDA 不是情感分类器,它做的是“主题发现”。在电商评论场景里,一个主题往往对应一个产品特征维度,比如“屏幕显示”“电池续航”“物流服务”“客服态度”。每个主题由一组高概率词构成,这些词就是特征词。项目里lda.py的核心任务就是跑 LDA,拿到每个主题下的特征词列表,为后续情感打分提供“打分维度”。这里有个选型理由值得说清楚:为什么不用 TF-IDF 直接提关键词?因为 TF-IDF 是词级别的权重,它不知道词与词之间的共现结构。LDA 通过文档-主题-词的三层贝叶斯结构,能把经常一起出现的词聚成主题,比如“屏幕”“色彩”“分辨率”“显示”会聚到一起,而 TF-IDF 只会给每个词单独打分,无法形成主题簇。

3.2 LDA 训练与主题数选择

from gensim import corpora, models import gensim def train_lda(docs, num_topics=5, passes=15): """ docs: 分词后的文档列表,每个元素是词列表 num_topics: 主题数,需要根据困惑度或人工评估调整 passes: 训练轮数,太少会导致主题不稳定 """ # 构建词典和词袋 dictionary = corpora.Dictionary(docs) # 过滤极低频和极高频词 dictionary.filter_extremes(no_below=3, no_above=0.8) corpus = [dictionary.doc2bow(doc) for doc in docs] # 训练 LDA 模型 lda_model = models.LdaModel( corpus=corpus, id2word=dictionary, num_topics=num_topics, passes=passes, random_state=42, alpha='auto', eta='auto' ) return lda_model, corpus, dictionary def print_topics(lda_model, num_words=10): """打印每个主题的高频词""" for idx, topic in lda_model.print_topics(num_words=num_words): print(f"主题 {idx}: {topic}") # 假设 docs 是预处理后的文档列表 # lda_model, corpus, dictionary = train_lda(docs, num_topics=5) # print_topics(lda_model)

filter_extremes这一步很关键。no_below=3表示出现次数少于 3 的词直接丢掉,no_above=0.8表示在超过 80% 文档里都出现的词也丢掉。这两个阈值是经验值,评论数据量小的时候可以放宽到no_below=2。alpha='auto'和eta='auto'让模型自动学习先验参数,省去手动调参的麻烦。random_state=42保证每次跑出来的主题顺序一致,方便对比实验。主题数num_topics没有标准答案,常见做法是跑 3 到 10 个主题,看哪个主题数下每个主题的词最“像人话”,就选哪个。

3.3 主题词到特征词的映射

LDA 输出的主题词里会混有一些通用词,需要人工筛一遍。比如主题 0 输出“屏幕 色彩 显示 效果 分辨率 不错 清晰”,其中“不错”是情感词不是特征词,应该归到情感词典里。我一般会建一个特征词映射表:

主题编号原始主题词筛选后特征词对应产品维度
0屏幕 色彩 显示 效果 分辨率屏幕 色彩 分辨率显示效果
1电池 续航 充电 耗电 快电池 续航 充电续航能力
2物流 快递 发货 速度 包装物流 快递 包装物流服务
3客服 态度 回复 售后 解决客服 售后客服体验
4价格 性价比 便宜 贵 划算价格 性价比价格感知

这张表是后续情感打分的“列向量”来源。每个特征词对应一个打分维度,评论里出现该特征词时,就去找它附近的情感词,做加权得分。

4. 情感加权打分:特征词与情感词的配对逻辑

4.1 打分模型的设计思路

项目摘要里提到“对于每一条评论相关特征名词相前后的情感副词、情感词进行加权得分”。这句话拆开就是三步:第一,定位特征词在句子中的位置;第二,在特征词前后窗口内找情感词和程度副词;第三,按副词强度给情感词加权,累加到该特征词的得分上。这个思路比整句情感分类更细,因为它能区分“屏幕好但续航差”这种混合评价。常见做法是建一个情感词典,每个情感词有一个基础极性值(+1 或 -1),程度副词有一个倍数(比如“非常”=1.5,“有点”=0.5,“太”=1.2)。

4.2 情感词典与程度副词表

# 情感词基础极性(示例,实际项目应扩充) sentiment_dict = { '好': 1, '不错': 1, '棒': 1, '满意': 1, '喜欢': 1, '差': -1, '烂': -1, '失望': -1, '垃圾': -1, '后悔': -1, '快': 1, '慢': -1, '清晰': 1, '模糊': -1, '便宜': 1, '贵': -1, '划算': 1, '值得': 1 } # 程度副词倍数 degree_dict = { '非常': 1.8, '很': 1.5, '太': 1.5, '特别': 1.6, '有点': 0.6, '稍微': 0.5, '比较': 0.8, '超': 1.7 } def score_sentence(sentence, feature_word, sentiment_dict, degree_dict, window=5): """ 在句子中定位特征词,前后 window 个词内找情感词和程度副词 返回该特征词在本句中的情感得分 """ words = list(jieba.cut(sentence)) if feature_word not in words: return 0 idx = words.index(feature_word) start = max(0, idx - window) end = min(len(words), idx + window + 1) context = words[start:end] score = 0 for i, w in enumerate(context): if w in sentiment_dict: base = sentiment_dict[w] # 看前一个词是不是程度副词 multiplier = 1.0 if i > 0 and context[i-1] in degree_dict: multiplier = degree_dict[context[i-1]] score += base * multiplier return score

window=5表示以特征词为中心,前后各取 5 个词作为上下文窗口。这个窗口大小可以根据句子平均长度调整,评论句子短的话 3 到 5 就够了。score_sentence返回的是单个特征词在单句中的得分,一条评论有多个句子时,把同一特征词的得分累加,就得到该评论在该特征维度上的总分。

4.3 构建评论得分 DataFrame

import pandas as pd def build_score_dataframe(comments, feature_words, sentiment_dict, degree_dict): """ comments: 评论列表 feature_words: 特征词列表,作为列名 返回:以特征词为列的得分 DataFrame """ records = [] for comment in comments: sentences = split_sentences(comment) row = {fw: 0 for fw in feature_words} for sent in sentences: for fw in feature_words: row[fw] += score_sentence(sent, fw, sentiment_dict, degree_dict) records.append(row) return pd.DataFrame(records, columns=feature_words) # 假设 comments 是原始评论列表,feature_words 是筛选后的特征词 # df_score = build_score_dataframe(comments, feature_words, sentiment_dict, degree_dict) # print(df_score.head())

这个 DataFrame 就是后续建模的输入特征矩阵。每一行是一条评论,每一列是一个产品特征维度的情感得分。得分有正有负,正值代表正面评价,负值代表负面评价。到这里,非结构化的评论文本就变成了结构化数值特征,可以直接喂给分类模型。

5. 建模与预测:LR、SVM、XGBoost 的对比与调参

5.1 特征工程:PCA 降维与皮尔逊相关分析

项目摘要里提到“通过 PCA、皮尔逊等提取特征数据进行建模”。PCA 的作用是降维,当特征词维度较高时(比如 20 个以上),特征之间可能存在共线性,PCA 可以把原始特征投影到低维空间,减少过拟合风险。皮尔逊相关分析则是用来筛选与目标变量(销量排名)相关性高的特征,把那些跟销量几乎没关系的特征维度去掉。常见做法是先算每个特征与销量排名的皮尔逊相关系数,保留绝对值大于 0.1 的特征,再做 PCA。

from sklearn.decomposition import PCA from scipy.stats import pearsonr import numpy as np def select_features_by_pearson(df_score, target, threshold=0.1): """按皮尔逊相关系数筛选特征""" selected = [] for col in df_score.columns: corr, _ = pearsonr(df_score[col], target) if abs(corr) >= threshold: selected.append(col) return selected def apply_pca(df_score, n_components=0.95): """PCA 降维,保留 95% 方差""" pca = PCA(n_components=n_components) transformed = pca.fit_transform(df_score) return transformed, pca # 假设 target 是销量排名标签 # selected_cols = select_features_by_pearson(df_score, target) # df_selected = df_score[selected_cols] # X_pca, pca = apply_pca(df_selected)

n_components=0.95表示保留 95% 的方差,具体保留几个主成分由数据决定。如果特征维度本来就不高(比如只有 5 到 8 个),PCA 可以跳过,直接拿原始得分矩阵建模。

5.2 三个模型的训练与对比

from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from xgboost import XGBClassifier from sklearn.metrics import accuracy_score, classification_report def train_and_evaluate(X, y): """训练 LR、SVM、XGBoost 并输出准确率""" X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) models = { 'LR': LogisticRegression(max_iter=1000, random_state=42), 'SVM': SVC(kernel='rbf', C=1.0, gamma='scale', random_state=42), 'XGBoost': XGBClassifier( n_estimators=100, max_depth=4, learning_rate=0.1, use_label_encoder=False, eval_metric='mlogloss', random_state=42 ) } for name, model in models.items(): model.fit(X_train, y_train) y_pred = model.predict(X_test) acc = accuracy_score(y_test, y_pred) print(f"{name} 准确率: {acc:.4f}") print(classification_report(y_test, y_pred)) # 假设 X 是特征矩阵,y 是销量排名标签 # train_and_evaluate(X_pca, y)

LR 适合做基线,训练快、可解释性强,系数能看出每个特征对销量排名的正负影响。SVM 在小样本高维数据上表现稳定,但核函数和 C 值需要调。XGBoost 是集成模型,通常准确率最高,但参数多,容易过拟合。max_depth=4是保守设置,防止树太深记住噪声。learning_rate=0.1配合n_estimators=100是常见组合,如果准确率不够可以适当增加树的数量,同时降低学习率。

5.3 模型对比表

模型优点缺点适用场景
LR训练快、可解释、不易过拟合对非线性关系拟合能力弱特征少、需要解释系数
SVM小样本表现好、核函数灵活参数敏感、大数据慢样本量几百到几千
XGBoost准确率高、自动处理缺失参数多、调参成本高追求精度、有调参经验

6. 避坑与排查:评论情感分析里最容易翻车的五个点

6.1 分词结果里混入大量单字和标点

现象:LDA 主题词里出现“的”“了”“一”“不”等单字,主题可解释性极差。原因:jieba 默认分词不会过滤单字,标点也会被切成独立 token。解决:在分词后加一层过滤,len(word) > 1且用正则排除非文字字符。停用词表要覆盖常见虚词和标点。

6.2 主题数设得太多导致特征词分散

现象:设了 10 个主题,每个主题只有两三个词,而且词与词之间看不出关联。原因:评论数据量不够大,主题数超过实际特征维度。解决:先用 3 到 5 个主题跑一遍,看主题词是否聚得起来。数据量少于 500 条评论时,主题数不要超过 5。

6.3 情感词窗口取得太大引入噪声

现象:特征词“屏幕”的得分里混入了隔壁句子的“物流差”情感。原因:window设得太大,跨句匹配。解决:先分句再打分,窗口只在句子内部滑动。window=5对大多数评论句子够用,句子特别长可以放宽到 7。

6.4 PCA 降维后特征完全失去可解释性

现象:用 PCA 降维后模型准确率上去了,但不知道哪个产品特征在起作用。原因:PCA 的主成分是原始特征的线性组合,物理含义模糊。解决:如果业务需要解释特征贡献,跳过 PCA,直接用皮尔逊筛选后的原始特征跑 LR,看系数。

6.5 销量排名标签不平衡导致模型偏向多数类

现象:XGBoost 准确率很高,但少数类(销量排名靠后)的召回率极低。原因:销量排名分布不均匀,高排名样本远多于低排名样本。解决:用stratify=y做分层采样,或者在模型里设class_weight='balanced',XGBoost 可以用scale_pos_weight调整。

7. 进阶技巧:用主题-情感矩阵做产品维度归因

跑完上面整套流程,你手里会有一个特征-情感得分矩阵。这个矩阵的价值不止于喂给分类模型。我习惯再做一步归因分析:把每个特征维度的平均得分按销量排名分组算出来,看高销量组和低销量组在哪些特征上差异最大。具体做法是用groupby按销量排名分箱,算每个特征列的均值,然后画横向对比。

def feature_attribution(df_score, sales_rank, n_bins=3): """ 按销量排名分箱,对比各特征维度的平均情感得分 """ df = df_score.copy() df['rank_bin'] = pd.qcut(sales_rank, q=n_bins, labels=['低', '中', '高']) attribution = df.groupby('rank_bin').mean().T attribution['差异'] = attribution['高'] - attribution['低'] return attribution.sort_values('差异', ascending=False) # 假设 sales_rank 是销量排名序列 # attr = feature_attribution(df_score, sales_rank) # print(attr)

pd.qcut按分位数把销量排名切成低、中、高三组,保证每组样本量接近。attribution['差异']列表示高销量组和低销量组在该特征上的得分差。差异为正且绝对值大的特征,就是高销量产品的优势维度;差异为负的,就是低销量产品的短板。这个结果可以直接拿去做运营建议,比如“高销量组在‘屏幕显示’维度平均得分比低销量组高 0.8,说明显示效果是拉开差距的关键特征”。

从那以后我每次跑完 LDA 和情感打分,都会强制走一遍这个归因表,不然模型准确率再高也不知道业务上该改什么。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询