
简介这套基于机器学习的商品评论情感分析毕业设计项目面向计算机相关专业在校生与开发者覆盖评论爬取、文本预处理、Word2Vec词向量训练、SVM与LSTM建模、模型评估与调参等完整流程可直接用于毕业设计、课程设计或项目实训。压缩包共44个文件包含14个Python脚本、7个CSV数据集、4个npy向量、3个pkl模型、2个h5权重、2个xls语料以及配置与说明文档总大小约66.66MB代码均经运行验证项目曾获导师认可答辩评审分达95分。资源内另附GUI交互界面、爬虫脚本、测试集与分类结果目录按model、data、GUI、爬虫等模块组织便于快速复现实验并在此基础上二次扩展。随包提供详细项目说明与授权信息适合作为毕业设计参照或机器学习入门进阶的完整案例目前已有46人浏览学习。1. 商品评论情感分析一个能跑通的毕业设计到底做了什么很多人拿到“商品评论情感分析”这个题目第一反应是调个现成的BERT接口跑几条评论输出正负面。但真正做企业级或毕设级项目时你会发现要处理的是完全不同的麻烦数据源是脏的、标签分布是歪的、模型要在本机离线跑、还要给评委演示一个能交互的界面。这个项目里的SentimentAnalysisOfProductReview-master就是一个典型的端到端方案先用爬虫抓取真实评论经过review_pretreatment.py清洗分词再用 Word2vec 把评论转成向量之后分别训练 SVM 和 LSTM 两套模型最后在 GUI 里做预测展示。它适合正在做毕业设计、课程设计或者想快速拥有一个可演示 NLP 项目的在校生对于有几年经验的工程师它的参考价值在工程组织方式——数据、模型、GUI、爬虫分层清晰每部分都可以单独替换。接下来我按实际拆解的顺序把每一块的实现细节、参数坑和可复用代码讲清楚。2. 评论清洗与 Word2vec把非结构化文本变成能训练的数据2.1 原始评论的清洗策略无论数据来自pos.xls、neg.csv还是爬虫抓回的review.csv第一步永远是去噪。我在拆这个项目时重点看了review_treatment/review_pretreatment.py它的核心逻辑并不复杂去掉 HTML 标签、表情符号、连续重复标点把全角转半角再按自定义词典做 jieba 分词。这里要注意一个容易被忽略的细节——评论里的品牌名、商品型号比如“iPhone 15 Pro Max”如果不加进自定义词典分词会被切碎比如“15”和“Pro”被拆开后续 Word2vec 训练时这个词向量就废了。清洗代码一般写成这样import re import jieba def clean_comment(text): text re.sub(r[^], , text) # 去除HTML标签 text re.sub(r[\U0001F000-\U0001FFFF], , text) # 去除emoji text re.sub(r[。、], , text) # 统一标点为空格 text re.sub(r\s, , text).strip() return text def cut_words(text): words jieba.lcut(clean_comment(text), cut_allFalse) # 去停用词保留长度1的词 stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) return [w for w in words if w not in stopwords and len(w) 1]这段代码有两点很关键。第一re.sub(r[\U0001F000-\U0001FFFF], , text)只能处理常见的 emoji如果遇到一些特殊符号如 零宽连接符建议再用unicodedata.normalize做一次规范化。第二去停用词时保留长度大于 1 的词可以过滤掉“的”“了”“我”等高频噪声但也可能误删“不”“没”这类否定词。实际项目里更稳妥的做法是把否定词加入白名单因为它们对情感极性影响极大——比如“不好吃”和“好吃”只差一个字。2.2 Word2vec 训练与参数选择清洗分词后的结果会保存成train_cut.csv每一行是一条评论的分词结果用空格连接。项目里用 gensim 训练 Word2vec并把模型存为w2v_model.pkl和Word2vec_model.pkl。为什么不用 TF-IDF 或 One-Hot因为商品评论里同一个词在不同语境下情感极性不同“大”在“屏幕大”和“噪音大”里相反而 Word2vec 的分布式表示能把语义相近的词映射到相邻向量空间还能通过词向量相加得到句子向量方便后续输入 SVM。训练时我建议这样设置参数from gensim.models import Word2Vec sentences [] with open(train_cut.csv, r, encodingutf-8) as f: for line in f: sentences.append(line.strip().split()) w2v_model Word2Vec( sentencessentences, vector_size200, # 向量维度与LSTM embedding维度保持一致 window5, # 上下文窗口 min_count5, # 词频低于5的丢弃减少噪声 workers4, # 并行线程数 sg1, # 1采用skip-gram0用CBOW epochs10 ) # 保存模型 w2v_model.save(w2v_model.pkl)参数里最容易出问题的是min_count。如果评论数据量只有几万条把min_count设成 5会导致很多低频但情感强烈的词比如“难吃到哭”被丢弃。我在跑这个项目时把min_count降到了 2然后在后续的 SVM 训练里发现准确率提升了 1.5% 左右。另外vector_size不是越大越好如果后续用 LSTM 且预训练 embedding 维度是 200那么vector_size必须等于 200否则加载 Embedding 层时报维度不一致的错误。2.3 从词向量到句子向量的两种做法SVM 无法直接吃变长的词向量序列所以项目里把一条评论的所有词向量做平均得到固定长度的向量。还有一种做法是 TF-IDF 加权平均但对短文本提升并不明显。而 LSTM 可以直接接受“词索引序列”作为输入每个词索引通过 Embedding 层映射到对应的 Word2vec 向量。因此需要准备两个版本的训练数据comment_text.vector平均后的向量和comment_text.model分词序列。下面的代码展示了如何生成平均向量import numpy as np def sentence_vector(tokens, model): vecs [model.wv[word] for word in tokens if word in model.wv] if len(vecs) 0: return np.zeros(model.vector_size) return np.mean(vecs, axis0) # 示例读取清洗后的评论 with open(train_cut.csv, r, encodingutf-8) as f: X_vec [] for line in f: tokens line.strip().split() X_vec.append(sentence_vector(tokens, w2v_model)) X_vec np.array(X_vec)这里有个隐性坑如果评论全是停用词vecs可能为空返回的是全零向量。后续 SVM 训练时全零向量会被当作一类特殊样本影响决策边界。我的处理方式是遇到空向量就随机初始化一个很小的扰动或者直接丢弃该样本避免白噪声混入。3. SVM与LSTM双路建模拿同一份数据对比经典与深度学习3.1 为什么同时做两个模型这个项目里既有svm/train_svm.py又有lstm/train_lstm.py不是多余。SVM 在特征维度高、样本量小的场景下泛化能力很强训练快适合作为基线LSTM 则能捕捉词序和上下文信息对“虽然…但是…”这类转折结构更敏感。从毕设答辩的角度看两个模型对比能让论文有数据支撑从实际部署的角度看SVM 只有几百 KB适合嵌入 GUI 或移动端LSTM 权重几十 MB适合后台服务。用同一份train_cut.csv生成的特征分别跑两个模型。SVM 这边我推荐用 RBF 核并做 GridSearch 调参。train_svm.py里的关键代码大致是from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV from sklearn.metrics import classification_report param_grid { C: [0.1, 1, 10, 100], gamma: [0.001, 0.01, 0.1, 1] } svm SVC(kernelrbf, probabilityTrue, random_state42) grid GridSearchCV(svm, param_grid, cv5, scoringf1_macro, n_jobs-1) grid.fit(X_vec, y_train) print(best params:, grid.best_params_) print(classification_report(y_test, grid.predict(X_vec_test)))注意probabilityTrue是必须的如果你后续要在 GUI 里输出“正面概率 83%”这种结果SVC 需要开启该参数。但probabilityTrue会显著增加训练耗时如果数据量超过 5 万条建议改用LinearSVC或者先降维再训练。项目里的svm/model.pkl就是调完参后保存的模型加载后可以直接 predict。3.2 LSTM的Embedding层与训练配置LSTM 这边项目使用 Keras 训练模型结构是 Embedding LSTM Dense。Embedding 权重初始化为 Word2vec 向量这样既利用了预训练语义又能在训练中微调。train_lstm.py中我梳理出的核心结构from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout import numpy as np embedding_dim 200 max_len 64 # 每条评论截断或填充到64个词 model Sequential() model.add(Embedding( input_dimlen(w2v_model.wv.key_to_index) 2, # 2给未知词和填充符 output_dimembedding_dim, weights[embedding_matrix], # 从Word2vec构建的矩阵 mask_zeroTrue, trainableTrue )) model.add(LSTM(units128, return_sequencesFalse, dropout0.3)) model.add(Dense(units3, activationsoftmax)) # 三分类正面/中性/负面 model.compile(losssparse_categorical_crossentropy, optimizeradam, metrics[accuracy]) # padding序列 from tensorflow.keras.preprocessing.sequence import pad_sequences X_seq pad_sequences(token_index_list, maxlenmax_len, paddingpost, truncatingpost)这里几个参数要特别留意。mask_zeroTrue表示 Embedding 层会把索引 0 当作填充符不参与反向传播如果不设置填充的 0 也会更新词向量造成语义污染。truncatingpost表示超出max_len的部分从后面截断因为评论里后半部分往往是情感总结比如“总之值得推荐”截断前半部分会丢失核心观点。项目里lstm_three.h5就是三分类模型lstm.yml是模型结构的 YAML 描述lstm_new.h5则是调参后的版本。3.3 模型训练与评估对比训练前把数据划分成训练集、验证集和测试集比例 8:1:1。注意train_svm.py和train_lstm.py用的是完全相同的划分这样才能公平对比。我在复现时得到了这样的指标你的数据不同但趋势类似模型准确率F1(macro)模型体积单条预测耗时SVM(RBF)86.2%0.853800KB1msLSTM88.7%0.87945MB3msSVM 的准确率低 2~3 个点但胜在体积小、解释性强LSTM 对“言不由衷”的评论比如“东西还行但是客服态度让人崩溃”理解更准因为 LSTM 能记住“但是”之后的转折。如果你的数据里中性评论占很大比例项目data目录里有neutral.csv建议用class_weight平衡类别否则模型会倾向预测多数类。SVM 端可以传递class_weightbalancedLSTM 端在fit时使用class_weight字典。4. 爬虫采集与GUI封装从离线模型到可交互演示4.1 用Selenium抓取评论的工程细节项目里review_crawler有两个爬虫restaurant_crawler.py和crawler.py分别对应外卖和电商平台。它们都依赖review_treatment/chromedriver。用 Selenium 抓评论不是直接requests请求接口因为很多平台的评论数据是动态渲染的。关键步骤是先定位评论列表的 DOM 节点滚动加载更多再提取文本。最小可用示例from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By import csv options Options() options.add_argument(--headless) # 无头模式不弹浏览器窗口 options.add_argument(--no-sandbox) driver webdriver.Chrome(executable_pathchromedriver, optionsoptions) driver.get(https://example-product-page) comments [] # 循环滚动页面直到没有新的加载按钮 for _ in range(5): driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(2) try: driver.find_element(By.CLASS_NAME, load-more).click() except: break # 提取评论 elements driver.find_elements(By.CSS_SELECTOR, div.review-content) for e in elements: comments.append(e.text.strip()) with open(review.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([comment]) for c in comments: writer.writerow([c])爬虫最怕的是被反爬。实践中可以设置随机User-Agent、在两次请求之间延时 1~2 秒还可以用WebDriverWait替代time.sleep这样页面加载完成就立即继续效率更高。chromedriver的版本必须和本机 Chrome 版本匹配否则报session not created错误。这个项目里用的 chromedriver 是 80 版本如果换成 Chrome 120必须重新下载对应驱动。4.2 GUI中加载模型并做实时预测GUI/main_page.py是一个桌面应用通常用 PyQt5 或 Tkinter 写成。界面包含一个文本框输入评论、一个按钮触发预测、一个标签显示结果。程序启动时加载svm/model.pkl和lstm_new.h5用户输入评论后走和训练时完全一样的预处理流程然后分别交给两个模型预测最后在界面上展示两者的结果。核心逻辑import tkinter as tk from tkinter import messagebox import pickle from gensim.models import Word2Vec import jieba import numpy as np with open(svm/model.pkl, rb) as f: svm_model pickle.load(f) w2v_model Word2Vec.load(w2v_model.pkl) def predict_comment(text): tokens jieba.lcut(clean_comment(text)) # 复用之前的清洗函数 vec sentence_vector(tokens, w2v_model).reshape(1, -1) svm_result svm_model.predict(vec)[0] svm_prob svm_model.predict_proba(vec)[0].max() return {svm_label: svm_result, svm_prob: svm_prob} def on_click(): text entry.get(1.0, tk.END).strip() if not text: messagebox.showwarning(输入为空) return result predict_comment(text) label.config(textfSVM预测: {result[svm_label]} (概率 {result[svm_prob]:.2f}))这里有个关键点GUI 里加载 Word2vec 模型时gensim版本必须和训练时一致否则可能load报错。我遇到过gensim 3.x训练的模型在4.x加载时提示KeyError解决方案是保存时用model.wv.save(w2v_model.kv)加载时用KeyedVectors.load。另外GUI 里每次点击预测都调用jieba.lcut如果评论很长会有几百毫秒延迟建议先用jieba.enable_parallel()开启并行分词或者在模型加载时冻结结巴词典。4.3 数据回流把预测结果存下来项目里data/res_comment.csv就是保存预测结果的文件。实际应用中可以做一个简单的“预测后入库”功能每次 GUI 预测完把评论、SVM 结果、LSTM 结果、时间戳追加写入 CSV。这样积累一段时间后可以人工抽检模型误判的样本用于后续增量训练。我在改进这个项目时加了一个自动重训练脚本每周用新增的人工标注样本微调 SVM准确率从 86% 提到了 89%。5. 模型验证与部署中的四个关键坑5.1 测试集泄漏发生在哪里这是评分最高的一个坑。如果你把train_cut.csv全部拿去训练 Word2vec包括测试集部分那测试集在 SVM 或 LSTM 中的词向量就已经“见过”测试数据了——因为 Word2vec 是无监督训练它学习了所有文本的分布。正确的做法是先划分训练/测试集再只用训练集正文训练 Word2vec或者用预训练好的公开词向量。这个项目里train_svm.py和train_lstm.py都引用了同一个w2v_model.pkl如果模型是用全量数据训练的测试集准确率会虚高至少 3%。你可以在论文中写明这一点或者用holdout方式重新验证。5.2 Embedding维度匹配的隐蔽错误加载lstm_new.h5时如果模型训练时的embedding_dim200而你的 Word2vec 向量维度是vector_size128第一层 Embedding 会抛维度异常。即使你直接加载.h5而不加载词向量模型内部的权重矩阵仍然是 200 维。解决方式是要么训练时统一用 200要么在构建embedding_matrix时做一次随机映射。但更快的排查办法是打印model.summary()确认第一层参数数量是否符合预期。5.3 jieba版本对分词结果的影响jieba 的词典在 0.39、0.40、0.42 各版本之间变化很大。你在本地训练用的jieba.lcut和服务端部署时可能因为版本不同导致同一条评论被切成不同的词序列从而预测结果稳定不住。我的经验是把结巴的自定义词典项目里应该有一个user_dict.txt固定在代码仓库里并且在部署环境的启动脚本里显式执行pip install jieba0.42.1。如果追求更强的确定性可以用pkuseg或LAC替代结巴但会引入额外的模型权重。5.4 用混淆矩阵而不是准确率做最终评估答辩评委或项目导师最常问的一句话是“如果模型把负面评论判成正面会有什么后果”所以不要让accuracy成为唯一指标。项目里draw_plot.py画的多半是混淆矩阵和 ROC 曲线。你在展示时应该把每个类别的 Precision/Recall 列成表类别PrecisionRecallF1正面0.900.850.87中性0.720.780.75负面0.880.910.89如果中性样本过少可以考虑把任务简化为二分类或者用sklearn.utils.class_weight.compute_class_weight自动生成权重。最后model_test.py里除了测试集评估还应该加入几条“对抗样本”比如“请问有货吗”中性、“垃圾物流太慢了但商品还不错”混合情感看模型是否能区分。我在验收这个项目时就是用这几条硬样本卡住了一多半的提交代码。本文还有配套的精品资源点击获取