简介:这份资源面向具备一定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 scorewindow=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 和情感打分,都会强制走一遍这个归因表,不然模型准确率再高也不知道业务上该改什么。希望帮到你。
本文还有配套的精品资源,点击获取