☰
商品评论情感分析:SVM与LSTM双路方案实战对比
2026/10/31 14:08:40 网站建设 项目流程

简介:本资源为基于机器学习SVM与LSTM的商品评论情感分析完整项目包,面向计算机相关专业毕业生及需要情感分析实战案例的学习者,可帮助完成毕设选题、算法对比实验与GUI演示系统搭建。包内共43个文件,涵盖14个Python源码(数据爬取、文本预处理、SVM与LSTM训练及绘图脚本)、7个CSV与2个XLS数据集、4个NPY与3个PKL中间文件、2个H5训练模型及YML配置等,压缩包约66.66MB,目录按svm_model、lstm、review_crawler、GUI等模块清晰划分。项目已获导师认可并通过严格调试,提供训练好的模型与可视化界面,读者可借此掌握词向量构建、SVM与LSTM对比实验、评论爬虫及情感预测全流程,适合作为毕设参考或课程设计模板。目前已有417人学习下载。

1. 商品评论情感分析:SVM 与 LSTM 双路方案到底怎么选

电商后台每天沉淀几万条评论,运营想快速知道「这批货到底哪里被骂了」,靠人眼翻根本不现实。这个标题指向的是一套完整的毕设级工程:用 Python 把中文商品评论做情感二分类(好评/差评),同时给出 SVM 和 LSTM 两条技术路线,配数据集、训练好的模型和 GUI 界面。它解决的不是「情感分析是什么」,而是「我怎么在本地把一套能跑、能演示、能改的系统搭起来」。适合两类人:一是做课程设计或毕设、需要完整可复现工程的学生;二是想快速验证「传统机器学习 vs 深度学习」在中文短文本上差异的初级算法工程师。SVM 靠 TF-IDF 特征就能跑出不错基线,LSTM 则能吃词序信息,两者放一起对比,正好把「特征工程路线」和「表示学习路线」的取舍讲清楚。下面按数据准备、SVM 路线、LSTM 路线、GUI 集成、避坑、进阶验证的顺序推下去,每一步都给可抄的命令和参数。

2. 数据准备与中文分词:把原始评论变成模型能吃的格式

2.1 数据集结构与标签定义

常见做法是拿到一份 CSV,字段至少包含review(评论文本)和label(情感标签)。标签一般用 0/1 表示,0 是差评、1 是好评。如果原始数据是 1-5 星评分,需要先做映射:1-2 星归 0,4-5 星归 1,3 星中性样本直接丢弃,避免边界模糊拉低上限。这一步别偷懒,我见过太多人把 3 星硬塞进某一类,最后模型在验证集上 F1 死活上不去,回头查半天才发现是标签噪声。

先确认数据规模和类别分布:

import pandas as pd df = pd.read_csv("data/reviews.csv", encoding="utf-8") print(df.shape) print(df["label"].value_counts(normalize=True)) print(df["review"].str.len().describe())

逻辑说明:value_counts(normalize=True)看类别是否均衡,如果某一类占比超过 80%,后面要么做重采样,要么在评估时看 F1 而不是准确率。str.len().describe()看评论长度分布,中文短文本常见长度在 10 到 200 字之间,超过 500 字的极少,可以截断。参数上,encoding一定要显式指定,Windows 下默认 gbk 读 utf-8 文件会直接报错。

2.2 中文分词与停用词处理

中文不像英文有空格,必须分词。常用jieba,加载自定义词典能显著改善领域词切分,比如「续航」「卡顿」「客服态度」这类词如果被切碎,TF-IDF 特征就废了。

import jieba import re def clean_text(text): text = re.sub(r"<.*?>", "", str(text)) # 去 HTML 标签 text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", " ", text) # 只留中英文数字 return text.strip() def tokenize(text): text = clean_text(text) words = jieba.lcut(text) stopwords = set(open("data/stopwords.txt", encoding="utf-8").read().split()) return [w for w in words if w not in stopwords and len(w) > 1] df["tokens"] = df["review"].apply(tokenize) df["seg"] = df["tokens"].apply(lambda x: " ".join(x))

逻辑说明:clean_text先去掉 HTML 和标点,避免噪声进入词表。tokenize里len(w) > 1过滤单字,中文单字多数是虚词或噪声,但注意「差」「好」这种单字情感词会被误杀,如果发现效果差,可以改成保留特定单字。停用词表建议用哈工大或百度停用词表,再手动加「商品」「东西」这类高频无区分度词。参数上,jieba.lcut是精确模式,比全模式更适合分类任务。

提示:分词后一定要人工抽 20 条看看切分结果,这一步花 5 分钟,能省后面几小时调参。

3. SVM 路线:TF-IDF 加线性核,十分钟拿到基线

3.1 特征工程:TF-IDF 的参数怎么设

SVM 吃的是数值特征,文本要先转成向量。TF-IDF 是最稳的选择,TfidfVectorizer几个关键参数直接决定效果上限。

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( df["seg"], df["label"], test_size=0.2, random_state=42, stratify=df["label"] ) tfidf = TfidfVectorizer( max_features=20000, # 词表上限,太大过拟合,太小欠拟合 ngram_range=(1, 2), # 一元+二元,捕捉「不 好用」这类组合 min_df=3, # 至少出现 3 次才进词表,滤掉长尾噪声 max_df=0.9, # 出现在 90% 以上文档的词直接丢 sublinear_tf=True # 用 1+log(tf) 抑制高频词 ) X_train_vec = tfidf.fit_transform(X_train) X_test_vec = tfidf.transform(X_test)

逻辑说明:max_features=20000是经验值,中文评论 5 万条以内够用。ngram_range=(1,2)能抓到否定搭配,但会让特征维度翻倍,如果内存吃紧就退回(1,1)。min_df=3和max_df=0.9是过滤噪声词和停用词的双保险。sublinear_tf=True对长评论更友好,避免某个词重复多次主导权重。注意fit_transform只在训练集上做,测试集必须用transform,否则就是数据泄露,这是血泪教训。

3.2 SVM 训练与核函数选择

from sklearn.svm import LinearSVC from sklearn.metrics import classification_report, f1_score svm = LinearSVC(C=1.0, class_weight="balanced", max_iter=5000) svm.fit(X_train_vec, y_train) pred = svm.predict(X_test_vec) print(classification_report(y_test, pred)) print("F1:", f1_score(y_test, pred, average="macro"))

逻辑说明:文本分类高维稀疏场景下,线性核LinearSVC比 RBF 核快一个数量级,效果通常还更好,因为文本特征本身就近似线性可分。C=1.0是正则强度倒数,C 越大越容易过拟合,建议在[0.1, 0.5, 1, 2, 5]里网格搜。class_weight="balanced"在类别不均衡时自动加权,比手动重采样省事。max_iter=5000防止不收敛警告,如果还报收敛问题,先检查特征是否做了归一化。

如果非要用 RBF 核,记住两个参数:gamma控制单个样本影响范围,文本任务里通常设1/num_features或更小;C和gamma要一起调,常用GridSearchCV在C=[1,10,100]、gamma=[0.01,0.1,1]上搜。但说实话,文本任务里 RBF 核调参收益远不如把分词和 TF-IDF 做好。

3.3 模型持久化与推理封装

import joblib joblib.dump(svm, "model/svm_model.pkl") joblib.dump(tfidf, "model/tfidf_vectorizer.pkl") def predict_svm(text): tokens = tokenize(text) vec = tfidf.transform([" ".join(tokens)]) label = svm.predict(vec)[0] return "好评" if label == 1 else "差评"

逻辑说明:模型和向量器必须一起保存,推理时先分词再transform,顺序不能乱。joblib比 pickle 对 numpy 数组更高效。封装成函数是为了 GUI 调用方便,输入原始字符串,输出中文标签。

4. LSTM 路线:词向量加序列建模,吃词序信息

4.1 词表构建与序列填充

LSTM 需要固定长度输入,先建词表把词映射成 ID,再统一截断或填充到同一长度。

from collections import Counter import numpy as np all_tokens = [w for tokens in df["tokens"] for w in tokens] counter = Counter(all_tokens) vocab = {w: i + 2 for i, (w, _) in enumerate(counter.most_common(30000))} vocab["<PAD>"] = 0 vocab["<UNK>"] = 1 MAX_LEN = 100 def encode(tokens): ids = [vocab.get(w, 1) for w in tokens][:MAX_LEN] return ids + [0] * (MAX_LEN - len(ids)) X = np.array([encode(t) for t in df["tokens"]]) y = df["label"].values

逻辑说明:most_common(30000)取最高频 3 万词,覆盖绝大多数评论。<PAD>用 0,<UNK>用 1,索引从 2 开始避免冲突。MAX_LEN=100覆盖大部分评论,超长截断、短则补零。如果长度分布尾部很长,可以取 95 分位数作为MAX_LEN,别拍脑袋定。

4.2 LSTM 模型结构与关键参数

import torch import torch.nn as nn class LSTMClassifier(nn.Module): def __init__(self, vocab_size, embed_dim=128, hidden_dim=128, num_layers=1): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers, batch_first=True, bidirectional=True, dropout=0.3) self.fc = nn.Linear(hidden_dim * 2, 2) def forward(self, x): emb = self.embedding(x) out, (h, _) = self.lstm(emb) h_cat = torch.cat([h[-2], h[-1]], dim=1) # 双向最后隐状态拼接 return self.fc(h_cat)

逻辑说明:embed_dim=128对中文评论够用,词表大可以加到 256。hidden_dim=128、num_layers=1是轻量配置,数据量小于 5 万条时层数多了必过拟合。bidirectional=True让模型同时看前后文,对情感任务有帮助。dropout=0.3加在 LSTM 层间,防止过拟合。取双向最后隐状态拼接而不是平均池化,是因为情感极性往往由关键几个词决定,最后状态保留信息更集中。

4.3 训练循环与早停策略

from torch.utils.data import DataLoader, TensorDataset dataset = TensorDataset(torch.LongTensor(X), torch.LongTensor(y)) loader = DataLoader(dataset, batch_size=64, shuffle=True) model = LSTMClassifier(len(vocab)) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.CrossEntropyLoss() best_f1, patience, wait = 0, 3, 0 for epoch in range(20): model.train() for bx, by in loader: optimizer.zero_grad() loss = criterion(model(bx), by) loss.backward() optimizer.step() # 每个 epoch 后在验证集上算 F1,这里省略验证代码 # 若 F1 不升,wait += 1;wait >= patience 则 break

逻辑说明:batch_size=64是显存和收敛速度的平衡点,显存小就降到 32。lr=1e-3是 Adam 的常用起点,训练不稳定就降到 5e-4。早停patience=3意思是验证 F1 连续 3 个 epoch 不提升就停,这是防止过拟合最省事的办法。注意 LSTM 训练时要把padding_idx=0的 embedding 梯度冻结,PyTorch 里设padding_idx后默认不更新该行,但保险起见可以手动embedding.weight.data[0] = 0。

注意:LSTM 在 CPU 上训练 5 万条数据大约需要 10-20 分钟每 epoch,如果时间紧,先用 SVM 出结果,LSTM 作为对比实验。

5. GUI 集成与避坑:从模型到可演示界面

5.1 Tkinter 最小可用界面

import tkinter as tk from tkinter import scrolledtext def on_predict(): text = input_box.get("1.0", tk.END).strip() if not text: return result = predict_svm(text) # 或 predict_lstm(text) output_label.config(text=f"预测结果:{result}") root = tk.Tk() root.title("商品评论情感分析") root.geometry("500x400") input_box = scrolledtext.ScrolledText(root, height=8) input_box.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) tk.Button(root, text="分析", command=on_predict).pack(pady=5) output_label = tk.Label(root, text="预测结果:", font=("Arial", 14)) output_label.pack(pady=10) root.mainloop()

逻辑说明:ScrolledText支持多行输入和滚动,适合粘贴长评论。按钮回调里先判空再预测,避免空字符串进模型报错。output_label动态更新文本。如果打包成 exe,用pyinstaller -F -w main.py,-w去掉控制台窗口,但注意模型文件要一起打包或放在相对路径下。

5.2 避坑与常见问题排查

现象一:SVM 训练完准确率 99%,换一批评论就崩。原因:训练集和测试集同分布,且可能做了数据泄露(比如先对全量数据 fit TF-IDF 再切分)。解决:严格先切分再fit_transform,并且用完全独立的真实评论做一次人工验证。

现象二:LSTM 损失不下降,一直卡在 0.69。原因:词表太大导致 embedding 稀疏,或者学习率过高导致震荡。解决:把词表降到 2 万,学习率降到 5e-4,检查标签是否从 0 开始(CrossEntropyLoss 要求类别是 0 到 C-1)。

现象三:分词后「不好用」被切成「不」「好用」,情感反转。原因:jieba 默认词典没有否定搭配。解决:把「不好用」「不推荐」加入自定义词典,或者用ngram_range=(1,2)让 TF-IDF 自己学组合特征。

现象四:GUI 点击分析后界面卡死。原因:模型推理在主线程执行,LSTM 首次加载慢。解决:把模型加载放在程序启动时,推理用threading.Thread丢到后台,或者至少加个「分析中」提示。

现象五:打包 exe 后报找不到模型文件。原因:pyinstaller打包后路径变了。解决:用sys._MEIPASS获取临时目录,或者把模型文件放到 exe 同目录用绝对路径读。

6. 进阶验证:用混淆矩阵和错误样本反推优化方向

模型跑通只是起点,真正决定这套方案值不值得投入的,是你能不能定位它错在哪。我一般会做两件事:画混淆矩阵看类别偏置,再抽 50 条错分样本人工归类。

from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt cm = confusion_matrix(y_test, pred) sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=["差评", "好评"], yticklabels=["差评", "好评"]) plt.xlabel("预测") plt.ylabel("真实") plt.show() # 抽错分样本 errors = X_test[pred != y_test] print(errors.head(20).tolist())

逻辑说明:混淆矩阵能一眼看出是差评被误判成好评多,还是反过来。如果差评召回低,说明模型对负面表达不敏感,可以检查停用词是否误删了「不」「没」这类否定词。错分样本里如果大量出现反讽(「真是太好了,用一天就坏」),那 SVM 基本没救,LSTM 加注意力机制可能还有提升空间。

一个具体技巧:把错分样本按评论长度分组,如果短评论错误率明显高,说明MAX_LEN或min_df把短文本的有效特征滤掉了,可以单独为短评论训练一个模型。另一个技巧是看 TF-IDF 权重最高的词,如果 top 词是「商品」「东西」这种无区分度词,说明停用词表没做好,回去补。

我自己踩过的坑是:一开始迷信 LSTM 一定比 SVM 强,结果数据量只有 8000 条时,LSTM 验证 F1 比 SVM 低 4 个点,后来把词表缩小、dropout 加到 0.5、早停 patience 降到 2,才勉强追平。所以别默认深度学习一定赢,小数据上特征工程才是王道。希望帮到你。

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

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

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

立即咨询