简介面向计算机相关专业学生的商品评论情感分析毕业设计项目采用机器学习与深度学习方法覆盖评论爬取、文本预处理、特征向量化、SVM与LSTM模型训练及结果可视化等完整流程适用于毕业设计、课程设计、项目初期演示或进阶学习。压缩包共44个文件以14个Python源码文件为核心同时包含CSV/XLS评论文本数据集、训练完成的h5与pkl模型文件、配置说明及演示脚本整体大小约66.66MB目录结构清晰便于按模块查阅。目前已有48人学习下载项目代码经测试运行正常另附详细文档与README方便快速理解算法原理与工程实现既可整体复现答辩项目也可在现有框架上修改参数或扩展功能满足不同阶段的实践需求。1. 商品评论情感分析为什么看着简单、做起来却容易翻车基于机器学习的商品评论情感分析是机器学习课程设计选题里出现频率最高的文本任务之一也是看起来门槛低、实际交付时最容易出问题的一类毕业设计。不少同学拿到项目的第一反应是直接上深度学习模型但以我做这类项目的经验来看模型只占工作量的小头数据标注、文本清洗、特征工程和实验流程设计才真正决定分数。这个课题的核心是监督分类把中文商品评论识别成好评、差评、中性三类解决的是“评论量大到人工看不过来”的问题适合想用最稳妥的方式完成毕设、又不想在部署环境上反复折腾的同学。2. 数据准备是第一步评论集怎么建、怎么标注、怎么平衡2.1 评论集从哪来公开数据配合自采小样本标注三类标签数据是情感分析的地基。在毕设场景里我一般建议“公开数据集为主、自采小样本为辅”的搭配公开的电商评论数据集能保证数据量但领域相对偏老自采小样本能体现你处理真实数据的能力又不会把时间全耗在抓取和反爬对抗上。字段至少包括评论内容、评分星级、商品类目其中评分星级是给评论打标签最直接的依据。import pandas as pd # 读取公开评论数据先确认字段名再动手 df pd.read_csv(reviews.csv, encodingutf-8) print(df.columns.tolist()) print(df.shape) # 常见做法按评分星级映射成三分类标签 def rating_to_label(star): if star 4: return positive elif star 3: return neutral else: return negative df[label] df[star].apply(rating_to_label) # 看一眼标签分布这一步几乎一定会触发不平衡处理 print(df[label].value_counts(normalizeTrue))这段代码里star列名按你自己的数据集为准先打印columns确认不要想当然。映射到三分类而不是二分类是因为“还行”“一般”这类中性表达在真实电商场景里占比不低硬塞进二分类会让模型在两个类别之间剧烈抖动。normalizeTrue输出的是每类占比如果positive超过六成下面的平衡处理就必须安排上。没评分的评论怎么办只能人工标注。标注时先定清楚“只看最终态度、不逐句断章取义”最好找两个人各标一遍再对结果这个细节第 5 章会展开。答辩时评委的第一个问题永远是“数据哪来的、怎么标、分布如何”提前把这段话准备成两分钟的叙述比临时翻代码有效得多。2.2 清洗与分词正则去噪、jieba 切词、停用词表要保留否定词所谓机器学习中的数据处理在这个项目里具体是清洗、分词、去停用词、数值化四件事。商品评论文本比新闻语料脏得多无意义符号、URL、HTML 标签、全角半角混用、重复标点经常混在一起必须先用正则做一轮噪声清理再交给分词器。import re import jieba def clean_review(text): text re.sub(r[^], , text) # 去掉 HTML 标签 text re.sub(rhttp\S, , text) # 去掉 URL text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text) # 只保留中文、英文、数字 text re.sub(r\s, , text).strip() return text # 自定义词典避免“性价比”被拆成“性/价/比” jieba.load_userdict(user_dict.txt) # 停用词表读取但“不、没、别、太、很”必须保留 STOPWORDS {的, 了, 吧, 啊, 呢, 呀, 嘛} with open(stopwords.txt, encodingutf-8) as f: for line in f: w line.strip() if w and w not in {不, 没, 别, 太, 很, 挺}: STOPWORDS.add(w) def cut(text): words jieba.lcut(clean_review(text)) return [w for w in words if w.strip() and w not in STOPWORDS]这里最关键的是第三行正则保留字母和数字是为了处理yyds、666这类网络词它们在评论文本里是情感信号不能当噪声丢掉。load_userdict要把“性价比”“掉电快”“售后”这类领域词加进去否则分词器会把它们拆碎TF-IDF 特征就再也拼不回来了。停用词表是下面最容易翻车的地方。“的、了、吧”确实该过滤但“不、没、别、太”这类否定词和程度词一旦被塞进停用词表“不好吃”会变成“好吃”整句情感直接反转。清洗完成后建议把分词结果打印十行人工过一眼确认“不好吃”没有被切开、yyds没有被丢掉。2.3 正负样本不平衡先看分布再决定欠采样还是类别加权电商评论的分布天然偏向好评。商品只要不是特别差四星五星能占六成以上差评常常不足两成。如果不处理模型学到的就是“把全部评论说成好评”的捷径测试集准确率看起来不错实际一个差评都抓不住。# 方法一统计并做简单的随机欠采样 from sklearn.utils import resample pos df[df[label] positive] neu df[df[label] neutral] neg df[df[label] negative] n min(len(pos), len(neu), len(neg)) pos_down resample(pos, replaceFalse, n_samplesn, random_state42) neu_down resample(neu, replaceFalse, n_samplesn, random_state42) neg_down resample(neg, replaceFalse, n_samplesn, random_state42) df_balanced pd.concat([pos_down, neu_down, neg_down]) print(df_balanced[label].value_counts()) # 方法二不删样本给模型损失加类别权重 from sklearn.utils.class_weight import compute_class_weight import numpy as np weights compute_class_weight( class_weightbalanced, classesnp.unique(df[label]), ydf[label] ) print(dict(zip(np.unique(df[label]), weights)))欠采样实现简单但会把大量好评样本直接扔掉数据量小的场景里有点浪费。相比之下类别加权是性价比更高的做法balanced会根据每类样本数自动放大少数类的错误惩罚训练时间不变数据一点不丢。毕业设计里我建议两种都跑一遍把结果做成对比表放进论文比单选一种更有说服力。样本分布和处理的截图从这一节开始就要按顺序存好。答辩材料的图表不是最后补的是每做一个环节顺手截的后面你会感谢这个习惯。3. 特征工程与模型选型先拿 TF-IDF 和传统机器学习把基线打出来3.1 TF-IDF 特征中文分词之后的向量空间评论经过清洗和分词后变成了词列表但分类器吃的是固定长度的数值向量。TF-IDF 就是把每条评论映射到词表上的稀疏向量TF 看词在当条评论里出现多少次IDF 看词在整个语料里稀不稀有两者相乘之后“性价比”这种有区分度的词会被抬起来“这个”这类泛化词会被压下去。商品评论本身很短低频词往往就是领域特征所以 IDF 在这个任务里特别有用。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( tokenizercut, # 复用上一章的分词函数 token_patternNone, # 避免与自定义 tokenizer 冲突 ngram_range(1, 2), # 保留“性价比高”这类二元短语 max_features8000, # 限制词表规模防止维度爆炸 sublinear_tfTrue, # 用 1log(tf) 平滑词频 min_df2 # 只保留至少出现在 2 条评论里的词 ) X vectorizer.fit_transform(df[content]) print(X.shape)ngram_range(1, 2)是短文本任务里最容易出效果的一档它保留“不是很好”这样的二元组合让否定结构不会被词袋完全打散。max_features从 5000 开始试即可不必盲目调大词表超过一两万后模型效果基本不再提升训练时间却成倍增加。sublinear_tfTrue的作用是防刷词。一条评论里写十遍“好”不意味着“好”的权重应当线性涨十倍用对数压缩更合理。min_df2把只出现一次的噪声词过滤掉新闻语料可以用 3 或 5但短评论用 2 更合适低了噪声多高了特征稀疏。3.2 表情符号与领域词典用低成本特征逼近多模态信息评论里信息密度最高的部分有时候不只在中文词里而在表情符号和网络变体词中。老用户习惯写“很满意”年轻用户可能只发“”或者一句“绝绝子”。把表情符号和文本联合起来建模是当前多模态情感分析在做的事但毕设阶段不需要上图像模型把这类信号手工提成特征列就够用。def emoji_score(text): # 统计表情里的正负向信号正向表情加分负向表情减分 return text.count() text.count() - text.count() - text.count() df[emoji_feat] df[content].apply(emoji_score) def neg_word_count(text): # 否定词数量是 TF-IDF 容易漏掉的全局线索 return sum(1 for w in [不, 没, 别, 无] if w in text) df[neg_feat] df[content].apply(neg_word_count) # 把两列特征拼接到 TF-IDF 稀疏矩阵右边 from scipy.sparse import hstack X_extra df[[emoji_feat, neg_feat]].values X_all hstack([X, X_extra]) print(X_all.shape)emoji_feat在“东西还行”这类样本里是救命的特征字面词是中性的但表情已经暴露了真实态度。neg_feat弥补词袋模型的一个天然短板——否定词被拆到不同位置后模型很难感知“不”到底修饰了谁而统计整条评论的否定词数量至少能给出全局倾向。特征拼接用scipy.sparse.hstack稀疏矩阵和稠密列可以直接合并sklearn 里的模型都能处理。唯一要注意的是特征列顺序必须固定训练和预测时保持同一套拼接流程否则模型上线后行为会变。3.3 三个基线模型的选择逻辑逻辑回归、朴素贝叶斯、SVM特征定了模型选型其实比想象中简单。文本经过 TF-IDF 后是高维稀疏向量线性模型在这种输入上最稳定训练也最快。我从逻辑回归起步因为它有概率输出、权重直接对应每个词对情感的贡献后续做错误分析时特别好用。模型特点训练速度可解释性毕设推荐度逻辑回归权重对应词汇贡献支持概率输出快高首选基线朴素贝叶斯小样本短文本表现好但概率估计偏激进很快中备选对照线性 SVM决策边界更锐利配合 C 调参可防过拟合中中效果不足时再上我一般先用逻辑回归把整个机器学习应用流程跑通输出一张“正面贡献最大词 / 负面贡献最大词”的表答辩演示时非常直观。SVM 在相同特征上通常比逻辑回归高 1 到 2 个百分点但网格搜索调C的训练时间会长不少。朴素贝叶斯速度最快可一旦特征做了 ngram 扩维它的概率估计容易偏激中性样本会被硬推向好差两极。这里要讲清楚一个反直觉的结论为什么优先传统机器学习而不是深度学习商品评论平均只有几十个字信息高度集中词序带来的增益有限。TF-IDF 加逻辑回归在这个规模的任务上普遍能到 85% 附近而 LSTM 在数据量不够、超参没调好的时候反而可能更差。深度模型的优势在于泛化到没见过的表达但毕设的数据量撑不起这种优势。深度学习适合放在论文的扩展讨论里主线用传统机器学习主动权在自己手里。4. 训练与评估用 Pipeline 调参、混淆矩阵定指标、从误判里找论文素材4.1 用 Pipeline 固定流程避免训练预测两套预处理特征工程一多最常见的坑就出现了训练时跑了一遍清洗加 TF-IDF预测新样本时忘了做同样的处理或者把fit_transform和transform混用结果测试集成绩虚高、上线一测完全不是一回事。Pipeline 的意义在于把向量化和分类器打包成一个整体fit 和 predict 自动走同一套流程。from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold X_train, X_test, y_train, y_test train_test_split( df[content], df[label], test_size0.2, stratifydf[label], random_state42 ) pipe Pipeline([ (tfidf, TfidfVectorizer( tokenizercut, token_patternNone, ngram_range(1, 2), max_features8000, sublinear_tfTrue, min_df2 )), (clf, LogisticRegression(max_iter1000, class_weightbalanced)) ]) # 类别不平衡时交叉验证必须分层抽样 cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) param_grid { tfidf__ngram_range: [(1, 1), (1, 2)], clf__C: [0.1, 1.0, 10.0] } grid GridSearchCV(pipe, param_grid, cvcv, scoringf1_macro, n_jobs2) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)stratifydf[label]保证切分后三类比例和全量接近差评本来就少随机切分很可能让测试集里没有差评导致评估结果完全失真。StratifiedKFold也是同样的逻辑交叉验证的每一折都保持类别比例分数才稳。param_grid里的ngram_range和C是逻辑回归在文本任务上最值得调的两个参数。C是正则强度的倒数C0.1惩罚更强适合特征噪声大的场景C10则更信任特征适合信息密度高的评论。建议三个档位都跑一遍不要凭感觉拍一个值。scoringf1_macro而不是accuracy原因下一节直接说明。4.2 评估指标怎么定混淆矩阵、F1 和按类别拆解的精确率召回率只看准确率是这个项目里最容易犯的错误。评论数据里好评占大头模型把全部评论都判成好评准确率也有六成以上看着不错实际一个差评都没抓到。做情感分析从第三天开始就要学会看混淆矩阵和按类拆解的精确率、召回率、F1。from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt y_pred grid.predict(X_test) print(classification_report(y_test, y_pred, target_names[negative, neutral, positive])) cm confusion_matrix(y_test, y_pred) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labels[negative, neutral, positive]) disp.plot() plt.savefig(confusion_matrix.png, dpi200, bbox_inchestight)classification_report会打印每个类别的精确率、召回率和 F1。重点看差评的召回率如果整体准确率很高但差评召回率低于 60%说明模型在真实场景里形同虚设。混淆矩阵的对角线是正确预测如果某一列明显偏大说明模型倾向于把评论硬分到那个类别比如全判成好评。模型做完后还要跟一个无脑基线对比把所有评论全部预测成多数类算它的准确率和 F1。模型必须明显高于这个基线才有意义。把基线、传统模型、深度学习模型做成一张三行对比表放进论文答辩时非常经得起追问。4.3 误判样本分析反讽、网络变体和口味的三个坑模型看错的样本不是随机的翻来覆去就那么几类。第一类是反讽“这质量真是绝了”字面看是正向实际传达的是强烈不满TF-IDF 丢掉了词序“真是绝了”里的正词全部被当成正面证据。第二类是网络变体“yyds”“绝绝子”“666”如果不在词表里就落不到任何特征上。第三类是口味主观描写“挺辣的”在火锅类目里是好评信号在婴儿零食里是差评信号而大多数训练集没有类目上下文。这三类误判在答辩时是加分项不是黑匣子。把误判样本收集成一张表格每条样本记录预测标签、真实标签、模型置信度和猜测原因然后在论文里单独写一节“错误模式分析”。主动讲清楚模型在哪里、为什么错、需要什么数据才能救比藏着掩着效果好得多。调参本身其实有点玄学味道同一个参数在不同数据集上表现可能完全相反。与其反复赌参数不如把精力花在误判分析上——每一类误判都对应一个特征工程方案比如加表情特征、补领域词典、加类目特征。这样模型迭代有方向论文素材也越攒越厚。5. 毕业设计避坑清单五个让项目返工的血泪教训5.1 标注一致性差两个人标出的结果对不上模型学不动现象请同学帮忙给无评分评论打标签同一句话一个人标好评、另一个人标中性训练集里全是噪声模型怎么调都停在 80% 以下。原因标注前没有统一规则混合态度句怎么处理没有约定比如“东西一般但物流很快”有人按整体态度标有人按最后一句标。解决开工前先抽 30 条做试标各标一遍后算标注重合率把不一致的条目逐条讨论形成书面规则。规则里明确“只看最终的购后态度”中性表达不要硬分好坏剩余争议样本用第三人投票解决。标注结果出来后再随机抽查 50 条做二次校验。5.2 数据泄露先 fit 全量语料再切分让测试集成绩虚高现象模型在测试集上准确率 92%部署到新数据上立刻掉到 75%前后差距大得离谱。原因一个非常隐蔽的操作——先拿全量评论算词频统计再切分训练测试集。这样测试集里的词汇信息已经渗入 TF-IDF 词表等于考试时提前看了答案。解决切分必须先于所有fit步骤。TfidfVectorizer只对训练集调用fit_transform测试集只用transform。自定义分词里如果用了全量语料训练的领域词典同样属于泄露用户词典只应基于训练集词频或者在论文里单独说明。检查方法是在干净环境里从原始文件重跑一遍完整流程对比前后分数。5.3 分词器版本不一致换台机器跑就报维度崩溃现象笔记本上模型正常交到另一台机器上加载后一预测就报维度不匹配或者报“特征数量不一致”。原因换机器的环境里 jieba 版本不同分词结果变了词表也跟着变训练时保存了 TF-IDF 矩阵却没有保存向量化器对象。解决requirements.txt里锁死jieba0.42.1和 sklearn 版本保存模型时把分类器和向量化器打包进同一个 pickle 文件。演示端加载同一个向量化器做transform绝对不要在新环境里重新fit词表。这个坑我踩过一次后果是全部重新训练。5.4 只看准确率92% 的准确率拦不住差评全漏现象测试集准确率 92%把验证集里的差评翻一遍十几条只抓出来两三条。原因好评占比太高模型学会了“无脑好评”的捷径准确率被多数类样本堆积支撑。解决评估指标换成 macro F1打印classification_report逐类看精确率和召回率训练时给逻辑回归加class_weightbalanced。论文里把混淆矩阵和分类别指标一起放进去不要只放一条准确率曲线。准确率唯一适合出现的位置是在和基线模型对比的表格里作为一行参考。5.5 一上来就上 LSTM显存爆掉、训练半天不收敛现象有人不甘心只用传统模型加了 word2vec 加 LSTM 的支线实验结果 GPU 显存直接不够内存也爆了一次。原因Embedding 层词表开得太大batch_size 设得太高长文本没有截断训练冗长且不收敛。解决词表卡在 5000 左右max_len统一截断到 64batch_size 降到 32加早停。更重要的是想清楚评论短、信息局部集中词序优势在这个任务上本来就不明显LSTM 的收益可能只有一两个点却要付出成倍的调试成本。主线用逻辑回归跑流程深度学习作为扩展实验只对比不大篇幅展开时间分配要清醒。6. 从模型到交付把分类器打包成页面服务、录好演示视频6.1 打包一个最小可演示的服务pickle 落盘 Flask 读取毕设验收时代码和文档再多都不如现场跑起来一条新评论有冲击力。我用 Flask 做一个最小演示服务页面输入一句评论后端加载训练好的模型返回情感类别和置信度。import pickle from flask import Flask, request, jsonify app Flask(__name__) # 模型和向量化器必须一起落盘只存分类器是常见的翻车操作 with open(sentiment_model.pkl, rb) as f: saved pickle.load(f) clf, vectorizer saved[clf], saved[vectorizer] app.route(/predict, methods[POST]) def predict(): text request.json.get(text, ) # 预测文本走和训练时相同的清洗与分词流程 X vectorizer.transform([cut(text)]) proba clf.predict_proba(X)[0] label str(clf.classes_[proba.argmax()]) return jsonify({label: label, prob: proba.tolist()}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)保存模型时用字典把分类器和向量化器包在一起pickle.dump({clf: ..., vectorizer: ...})加载时再一起取出来。只存分类器而丢掉词表是这类项目最常见的后悔药场景。Flask 做演示已经足够不需要上 gunicorn 和 Docker。host0.0.0.0让你可以在演示当天用手机连同一局域网访问页面现场自由度更高演示效果也更好。6.2 演示视频与论文写作把误判案例当亮点写进去录演示视频前先准备 8 到 10 条新评论覆盖明显的正负面、中性表达以及一条反讽句。开场先展示混淆矩阵和分类别指标再切到服务页面逐条输入新评论。遇到模型误判反讽句不要慌张直接说“这个问题出在词袋模型缺少词序信息是这类方法公认的边界”这一段恰恰是最能体现你理解深度的地方。论文写作时不要只贴代码和准确率。把三块内容串成主线一是基线模型与最终模型的对比表说明每一处特征或参数带来多少提升二是消融实验去掉表情特征再看指标变化三是误判分析表配上 5 到 8 个典型样本。这三块做好论文的含金量会明显高于同题目的平均水平。我自己做这个题时最后悔的是把前两周耗在调深度学习模型上后来换回传统模型加完整评估流程三天就把效果和论文素材补齐了。演示视频也因为只录了静态页面重录了一次。一个项目的成败很多时候不在于模型多先进而在于每一步有没有留下证据、有没有把边界摸清楚。希望这次整理的流程能帮你少走这几步弯路。本文还有配套的精品资源点击获取