简介:面向毕业设计、课程大作业及机器学习入门实践,这是一份基于机器学习的电商淘宝商品评论情感分析项目源码与数据包。项目从Selenium模拟登录爬取淘宝评论入手,依次完成数据清理、jieba精确模式分词、词语索引与词向量构造,并对比SVM与LSTM两种分类模型,形成了完整的实验闭环。压缩包共43个文件,以14个Python脚本为核心,涵盖爬虫、预处理、训练、测试与GUI主界面;同时附带csv/xls原始与处理后数据、pkl向量与模型文件、h5深度学习模型、yml配置及chromedriver驱动,包体约66.68MB,结构清晰便于直接运行与二次开发。目前已有382人学习下载。该资源还包含评论文本模型、Word2vec模型、SVM与LSTM多套模型及实测结果数据,适合希望快速复现情感分析流程、理解机器学习建模细节或扩展自身毕设项目的计算机相关专业学生使用。
1. 淘宝商品评论情感分析项目到底在分析什么
很多人拿到这个题目的第一反应是"训练一个分类器把好评差评分出来",但实际做下来你会发现,真正的难点不在模型,而在数据。把 Python、机器学习、情感分析这三个词串起来的淘宝商品评论项目,核心链路是从评论文本导入、中文分词、特征向量化,到分类器训练、参数调优和结果可视化——每一步都有比想象中更多的细节。淘宝评论短促、口语化且夹杂大量促销噪声,直接套用通用情感词典准确率往往不到六成,必须用数据预处理和特征工程把它变成机器学习能吃下的格式。这个项目适合用 Python 做毕业设计,也适合作为 NLP 入门后的第一个完整工程。下面按我实际做这类项目时会走的顺序展开。
2. 评论文本预处理:从原始数据到可训练语料
2.1 数据来源与存储:先梳理字段再动手
常见做法之一是直接使用公开的评论数据集,一些数据竞赛平台上有现成的脱敏淘宝评论数据可以下载;如果没有现成数据,就对着商品详情页抓取评价接口。抓取前先把字段结构定下来,我从一开始就会让每条评论至少包含 comment_id、item_id、rating、content、create_time 五个字段。字段的作用各不相同:comment_id 是去重主键,item_id 用于按商品分组评估,rating 是自动标注的依据,content 是模型输入的文本主体,create_time 留作后续时间维度的分析。
| 字段 | 示例 | 用途 |
|---|---|---|
| comment_id | 1234567890 | 去重主键 |
| item_id | 598123456789 | 关联商品,按商品分组切分数据 |
| rating | 5 | 监督标签来源 |
| content | "质量很好,物流快" | 文本主体 |
| create_time | 2024-03-15 | 时间分布分析 |
抓取环节有一个现实约束:淘宝评价接口的登录态 Cookie 有效期短、分页参数带签名校验,从零写爬虫的成本不低。我一般会直接拿成熟的开源爬虫脚手架改造,把精力放在数据整理而不是反爬对抗上。数据落地我会存成 CSV 而不是 JSON,pandas 读取方便,万级以内的评论量完全够用,中途想用 Excel 快速扫一眼数据也顺手。
import pandas as pd df = pd.read_csv('taobao_comments.csv', encoding='utf-8-sig') df = df.drop_duplicates(subset=['comment_id']) df = df[df['content'].notna() & (df['content'].str.strip() != '')] df['content'] = df['content'].astype(str).str.strip() print(f"有效评论数: {len(df)}")提示:drop_duplicates 解决的是同一用户追评产生的重复记录;content 为空的行是只打星不写字的评论,对训练没有贡献,直接丢掉。
这段代码跑完后,先看一眼 rating 的分布。淘宝的评分是 1 到 5 分,4 分和 5 分都算好评,2 分和 1 分是差评,3 分是中性。把 1-2 分归为负向、3 分归为中性、4-5 分归为正向,是比较常见的基线映射。不要直接把 1-5 分当成回归目标来训练,因为评分和文本情感并不严格线性相关,"质量不错但物流慢"给 4 分的场景很常见。
2.2 中文分词与清洗:jieba 的边界在哪
中文评论没有天然空格,分词绕不开。jieba 是主流选择,默认词典偏通用领域,处理"宝贝""卖家""物流"这类淘宝高频词基本没问题,但对"性价比""客服态度"这类复合词容易切碎。项目里我会维护一个自定义词典,把领域复合词连同词频、词性写进去,让结巴优先按整词切分。
import jieba # 自定义词典格式: 词 词频 词性 custom_words = [ "性价比 100 n", "客服态度 80 n", "物流速度 80 n", "质量杠杠的 60 a", ] with open('custom_dict.txt', 'w', encoding='utf-8') as f: f.write('\n'.join(custom_words)) jieba.load_userdict('custom_dict.txt') def tokenize(text: str) -> list: text = text.replace('\u3000', ' ').strip() # 全角空格归一 return jieba.lcut(text)词频数字设一个相对值即可,jieba 拿它和其他候选切分做权衡,写 100 还是 80 不会明显影响结果,但词性和整词本体一定要写对。清洗阶段,除了常规的去 HTML 标签、去 URL、去数字,还要处理两类淘宝评论特有的噪声:一类是"帮朋友买的,还没问"这类中性灌水,另一类是"物流很快!物流很快!物流很快!"这种重复刷屏。前者在标注时归入中性,后者用正则先压缩连续重复片段再做分词。另外全角半角标点要统一成半角,否则标点过滤会漏掉一半。
2.3 停用词表:不要直接抄网上的
通用中文停用词表在评论场景有一个明显的坑:它会把"不""没"这类否定词也停用掉,而"不怎么样"和"怎么样"的情感极性完全相反。停用词表在这里只应该负责移除标点、语气词、助词,凡是列表里是否定词或程度副词的条目,我会直接删掉。
import re STOPWORDS = set(open('stopwords.txt', encoding='utf-8').read().splitlines()) def clean_tokenize(text: str) -> list: text = re.sub(r'[a-zA-Z0-9]', '', text) # 去字母数字 text = re.sub(r'[\s+\.\!\?\[\]【】]', '', text) # 去标点 tokens = jieba.lcut(text) return [t for t in tokens if t not in STOPWORDS and len(t) >= 2]长度过滤设成 2 会丢掉"好""差"这类单字评价词,而它们在淘宝评论里恰恰信息量很高。我的做法是单独维护一个强情感单字表,比如"好、坏、差、烂、赞",分词命中这些单字时强制保留,其余单字一律过滤。这一步做完,把词频统计打印出来看一遍,比任何理论都直观——如果"了""的"还排在前五,说明停用词表没生效或者加载路径写错了。
3. 特征工程与分类器选型:TF-IDF 之外的选择
3.1 TF-IDF 向量化的两个关键参数
清洗后的评论是一串词列表,下一步要决定怎么变成机器学习分类器能接受的向量。对这个量级的短文本,TF-IDF 比纯词频计数更合适:它会抑制"淘宝""物流"这类高频词的权重,把"惊艳""失望"这类有判别力的词突出来。我用的向量化写法如下:
from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer( max_features=5000, # 词表上限,防止维度爆炸 ngram_range=(1, 2), # 保留相邻词的组合特征 min_df=2, # 只在1条评论里出现的词直接丢弃 max_df=0.8, # 80%以上评论都出现的词不建特征 sublinear_tf=True, # 用1+log(tf)压缩高频词优势 ) X = vectorizer.fit_transform([' '.join(tokens) for tokens in all_tokens])max_features 的经验取值是 3000 到 8000:太小丢信息,太大会把商品型号、颜色等噪声词也带进来。ngram_range 取 (1,2) 能捕获"不 错""太 慢"这类短语层面的局部共现,三元及以上在这个长度的评论上收益很小,反而让特征矩阵爆炸。min_df=2 过滤掉只在单条评论中出现的词,max_df=0.8 过滤掉几乎所有评论都出现的平台通用语,这两个阈值配合使用,能砍掉三到四成的无效特征。
3.2 分类器对比:先跑基线再谈深度学习
不少毕设直接上 LSTM 甚至 BERT,但几万条级别的短文本评论,传统机器学习算法往往已经能到 85% 以上的准确率。我的流程是先用几个基线分类器把数据整体摸一遍,判断它有多线性可分。这些算法的原理在周志华《机器学习》和李宏毅的课程里都有覆盖,文献综述部分可以直接引用。
| 模型 | 训练耗时(万条级) | 准确率参考 | 适用场景 |
|---|---|---|---|
| 逻辑回归 | 秒级 | 85%-88% | 基线首选,能输出概率 |
| 多项式朴素贝叶斯 | 秒级 | 82%-86% | 独立性假设强,短文本友好 |
| 线性 SVM | 秒级 | 86%-89% | 类别不平衡时表现更好 |
| RBF SVM | 分钟级 | 88%-90% | 非线性边界,gamma 要调 |
如果逻辑回归已经到 86% 以上,先别急着换模型,回去看错误样例,大部分提升空间在数据和特征侧而不是模型侧。对比代码用五折交叉验证,指标取 f1_macro 而不是 accuracy——评论数据好评占比经常超过六成,只看准确率会被多数类掩盖。
from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import cross_val_score models = { 'lr': LogisticRegression(max_iter=1000), 'nb': MultinomialNB(), 'svm_linear': SVC(kernel='linear'), } for name, model in models.items(): scores = cross_val_score(model, X, y, cv=5, scoring='f1_macro') print(f"{name}: {scores.mean():.4f} (+/- {scores.std():.4f})")3.3 类别不平衡:差评稀少恰恰是重点
淘宝评论天然好评多、差评少,常见比例在 7:2:1 附近。如果直接训练,模型学到的是一条"无脑预测好评"的捷径,少数类的 F1 会非常难看。处理顺序上我建议这样试:先加 class_weight='balanced',逻辑回归和 SVM 都内置支持,一行代码按类别频率倒数加权;这一步无效再考虑阈值移动。
SMOTE 这类合成采样方法在 TF-IDF 高维稀疏向量上效果并不好,插值生成的样本在特征空间里没有实际语义,我在这个项目里试过之后放弃了。阈值移动的写法如下:
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42 ) model = LogisticRegression(class_weight='balanced', max_iter=1000) model.fit(X_train, y_train) probs = model.predict_proba(X_test)[:, 1] # 正向概率 pred_adjusted = (probs > 0.45).astype(int) # 阈值向下调整阈值不是拍脑袋定的,把 0.3 到 0.6 按 0.05 步长遍历,画出少数类 F1 随阈值变化的曲线,峰值对应的点就是当前数据上的最优阈值。注意阈值移动只改预测边界不改模型权重,交付项目时记得把这个阈值也写进配置文件,避免换环境后结果对不上。
4. 情感分析模型的训练、评估与调参闭环
4.1 划分训练集时的坑:按商品分组而不是按行切
先做一个容易被忽略的决定:训练集怎么切。如果直接按行随机切分,同一件商品的评论会同时出现在训练集和测试集里,模型等于在测试时偷看了同商品下其他评论的表达习惯,分数虚高。严谨的做法是按 item_id 分组,确保同一商品的评论只落在一侧。
from sklearn.model_selection import GroupKFold groups = df['item_id'].values gkf = GroupKFold(n_splits=5) for train_idx, test_idx in gkf.split(X, y, groups): X_train, X_test = X[train_idx], X[test_idx] y_train, y_test = y[train_idx], y[test_idx] break # 演示只取第一折GroupKFold 的每折都是一个完整的商品分组切分,评估结果更贴近真实泛化场景。test_size 取 0.2 是惯例,但 stratify 参数一定要带上,让训练集和测试集的类别分布保持一致,否则类别不平衡问题会在评估阶段被放大。
4.2 三个必调的模型参数
4.2.1 逻辑回归:C、penalty 与 solver 的联动
默认参数下逻辑回归跑到 85% 问题不大,想再往上就得动三个参数。第一个是 C,正则化强度的倒数,C 太小欠拟合、太大过拟合,用 GridSearchCV 在 [0.1, 1, 10] 上搜,通常 C=1 附近最优。第二个是 penalty,默认 L2;特征维度高且稀疏时换成 L1,会把不重要的特征权重压成 0,等于顺手拿到一个可解释的特征选择结果。第三个是 solver,万级数据用 lbfgs 足够,数据量再大建议 saga;需要留意的是 saga 对 L1 的支持方式和 lbfgs 不同,改 penalty 时要确认 solver 兼容。
4.2.2 SVM:C 与 gamma 要一起搜
linear 核的 SVM 只需要调 C;换成 RBF 核后,gamma 默认值按 1/n_features 计算,在 5000 维特征下数值极小,模型会明显欠拟合。我一般把 gamma 固定在 0.01 到 0.1 区间,再和 C 一起做网格搜索,而不是单独调其中一个。调参结束后,在测试集上用同一个 random_state 做一次完整评估,保证交叉验证和最终报告的口径一致。
4.3 混淆矩阵与错误案例分析
评估阶段我固定输出三样东西:混淆矩阵、分类报告、高置信错误样本清单。混淆矩阵能直观看出哪些差评被判成好评——这类错误通常集中在"质量还可以吧"这种句子,模型学到"质量"的正面关联,却没学到"还可以"背后的勉强语气。一次典型训练的混淆矩阵格式如下(数字仅示意):
| 预测 \ 实际 | 负向 | 正向 |
|---|---|---|
| 负向 | 812 | 76 |
| 正向 | 143 | 2396 |
from sklearn.metrics import confusion_matrix, classification_report y_pred = model.predict(X_test) cm = confusion_matrix(y_test, y_pred) print(cm) print(classification_report(y_test, y_pred, target_names=['neg', 'pos'])) # 找高置信但预测错误的样本 probs = model.predict_proba(X_test) prob_neg = probs[:, 0] wrong_high = (y_test == 1) & (y_pred == 0) & (prob_neg > 0.8) print(X_test[wrong_high][:5])把这些样本打出来逐条读,错误高度集中在三类:讽刺句("真是太好了,三天还不发货")、对比句("比实体店便宜,但质量差多了")、程度副词修饰句("还算满意")。这三类观察写进论文的误差分析章节,比套模板可信得多。
5. 用 LIME 对商品评论情感分析做单条验证
答辩评委大概率会问"为什么这条差评被判成了好评"。LIME 能给出词级解释:对单条评论做随机扰动,观察哪些词的变化改变了预测,从而输出每个词对情感倾向的贡献方向。用 Python 跑 LIME 的写法如下:
import lime import lime.lime_text explainer = lime.lime_text.LimeTextExplainer( class_names=['负向', '正向'], split_expression=lambda x: list(jieba.cut(x)), # 必须与训练分词一致 bow=False, ) def predict_proba(texts): return model.predict_proba(vectorizer.transform(texts)) exp = explainer.explain_instance( "物流慢到离谱,客服也爱答不理,差评", predict_proba, num_features=10, ) exp.save_to_file('lime_explanation.html')两个关键点。split_expression 必须和训练时的分词逻辑一致,否则 LIME 生成的扰动文本在向量化后对不上原始特征,解释结果会失真。bow=False 让 LIME 保留词序做扰动,在短文本上比词袋模式更符合直觉。LIME 输出的典型结果里,"慢""离谱"把预测推向负向,"客服"本身中性,但因为上下文是"爱答不理",扰动实验中会表现出弱的负向贡献。
如果 LIME 结果和主观判断大面积冲突,优先怀疑训练数据的标注质量,而不是模型结构。反过来,它也能指导特征工程:当 LIME 反复把"店"标成强负向词时,往往是"黑店""坑店"的负面语境淹没了"旗舰店"的正面语境,把"旗舰店"合并成单个 token 再重训,少数类 F1 常常能回升。最后把 LIME 的权重按类别汇总画成柱状图或词云,直接放进论文的图表目录,答辩时逐条展示比只念准确率更有说服力。
本文还有配套的精品资源,点击获取