
简介本资源为基于机器学习SVM与LSTM的商品评论情感分析完整毕设项目面向计算机、人工智能相关专业学生及需要情感分析实战案例的开发者可解决从数据采集、文本预处理到模型训练与可视化交互的全流程问题。压缩包共43个文件约66.66MB包含14个Python源码文件、7个CSV与2个XLS数据集、4个NPY及3个PKL模型文件、2个H5权重文件另有配置文件、词向量模型与GUI界面脚本覆盖爬虫、分词、训练、测试与绘图模块。项目已获导师认可并通过严格调试提供训练好的SVM与LSTM模型可直接运行验证效果。已有417人学习下载适合作为毕业设计参考或情感分析入门实践帮助读者快速理解传统机器学习与深度学习在文本分类中的对比实现掌握从数据到界面的完整工程链路。1. 商品评论情感分析SVM 与 LSTM 双路方案到底解决了什么问题电商后台每天沉淀几万条评论运营真正想知道的不是「有几条差评」而是「差评在骂什么、好评在夸什么、这批货还能不能救」。人工翻页看评论一天最多处理几百条还容易漏掉关键信号。基于机器学习 SVM 与 LSTM 的商品评论情感分析本质是把「读评论」这件事拆成两步先把中文文本转成模型能吃的向量再用分类器判断这条评论是正面还是负面。SVM 负责在小样本、高维稀疏特征上快速给出稳定边界LSTM 负责捕捉「不、没、太」这类词序敏感的长距离依赖。两条路线各有适用场景合起来才是一套能落地的方案。这套东西适合三类人做毕设需要完整闭环源码数据集训练好的模型GUI 界面的学生想给自家店铺做评论监控的中小商家以及刚入门 NLP、想找一个能跑通全流程项目的开发者。它不追求 SOTA追求的是「今天下载、明天能跑、后天能改」。下面从数据准备、SVM 路线、LSTM 路线、GUI 集成到避坑一步步拆开讲。2. 数据准备与中文分词把评论变成模型能吃的格式2.1 数据集长什么样、怎么划分才不翻车商品评论数据集通常是「评论文本 情感标签」两列标签用 0/1 或 0/1/2 表示负面/中性/正面。拿到数据第一件事不是急着训练而是看分布。如果正面 9000 条、负面 1000 条直接训练出来的模型会倾向于全预测正面准确率看着有 90%实际召回惨不忍睹。常见做法是先做类别统计再决定是否过采样或调整class_weight。划分比例我一般用 8:1:1训练集、验证集、测试集分开。注意验证集和测试集必须从同一分布里随机抽不能按时间顺序切——除非你就是要做「用历史预测未来」的时间外推实验。下面这段代码做三件事读数据、看分布、分层划分。import pandas as pd from sklearn.model_selection import train_test_split # 读取评论数据假设列名为 review 和 label df pd.read_csv(comments.csv, encodingutf-8) print(df[label].value_counts()) # 先看类别分布别跳过这步 # 分层抽样保证训练/验证/测试集类别比例一致 train, temp train_test_split( df, test_size0.2, stratifydf[label], random_state42 ) val, test train_test_split( temp, test_size0.5, stratifytemp[label], random_state42 ) print(len(train), len(val), len(test))stratify参数是关键它让每个子集的标签比例和原始数据一致避免某个类别在验证集里消失。random_state固定后结果可复现调参时不会因为换了个随机种子就以为模型变好了。2.2 中文分词与停用词jieba 的三个必调参数中文不像英文有空格必须先分词。jieba 是最常用的选择但默认模式对评论这种口语化文本不够友好。我一般会做三件事加载自定义词典把商品名、品牌词加进去、过滤停用词、保留否定词。否定词一旦被当停用词删掉「不好用」就变成「好用」情感直接反转这是血泪教训。import jieba # 加载领域词典避免商品名被切碎 jieba.load_userdict(user_dict.txt) stopwords set(open(stopwords.txt, encodingutf-8).read().split()) # 否定词必须保留不能进停用词表 negations {不, 没, 无, 非, 别, 太} def cut_text(text): words jieba.lcut(str(text)) return [w for w in words if w not in stopwords or w in negations] df[tokens] df[review].apply(cut_text) print(df[tokens].head())load_userdict接受每行「词 词频 词性」格式的文件词频可以省略。停用词表建议用哈工大或百度的那份再手动把否定词捞出来。分词后可以看一眼高频词如果发现「的」「了」还在说明停用词表没生效。2.3 特征工程TF-IDF 与词向量的选择依据SVM 吃的是数值特征最常用的是 TF-IDF。它把每条评论变成一个稀疏向量词的重要性由「在本文出现频率 × 在全语料稀有程度」决定。max_features控制词表大小太大容易过拟合太小丢信息评论数据一般取 5000 到 20000 之间。ngram_range设成 (1,2) 能把「不好」「很好」这种二元组合保留下来对情感任务帮助明显。from sklearn.feature_extraction.text import TfidfVectorizer tfidf TfidfVectorizer( max_features10000, # 词表上限评论数据 1 万够用 ngram_range(1, 2), # 保留二元词组捕捉否定搭配 min_df2, # 出现少于 2 次的词直接丢 tokenizerlambda x: x, # 传入已分好的词 preprocessorlambda x: x, token_patternNone ) X_train tfidf.fit_transform(train[tokens]) X_val tfidf.transform(val[tokens])注意fit_transform只在训练集上调用验证集和测试集必须用transform否则词表里混入了验证集信息评估结果会虚高。这是新手最容易踩的坑之一模型上线后效果掉一大截往往就是这里出的问题。3. SVM 路线小样本高维场景下的快速基线3.1 为什么评论情感分析里 SVM 还没过时深度学习满天飞的年代还讲 SVM不是守旧。商品评论数据集往往只有几千到几万条标注成本高这种规模下 SVM 配合 TF-IDF 经常能打到和 LSTM 差不多的 F1训练时间却只要几秒。SVM 的核心思想是找一个超平面让两类样本之间的间隔最大化落在间隔边界上的少数样本叫支持向量最终模型只由这些支持向量决定。高维稀疏的文本特征里很多词对分类没贡献SVM 的核技巧和正则项天然抗噪。选核函数是第一个决策点。线性核适合特征维度远大于样本数的场景文本分类基本都用它RBF 核能拟合非线性边界但参数多、训练慢评论数据上不一定划算。我一般先跑线性核效果不够再试 RBF。3.2 用 GridSearchCV 调 C 和 gamma 的最小命令SVM 的两个核心参数是C和gamma。C越大对误分类的惩罚越重边界越窄容易过拟合C越小容忍度越高可能欠拟合。gamma只在 RBF 核里起作用控制单个样本的影响半径。下面用网格搜索在验证集上找最优组合。from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV from sklearn.metrics import classification_report # 先跑线性核基线 svm_linear SVC(kernellinear, C1.0, class_weightbalanced) svm_linear.fit(X_train, train[label]) pred_val svm_linear.predict(X_val) print(classification_report(val[label], pred_val)) # RBF 核网格搜索 param_grid {C: [0.1, 1, 10], gamma: [scale, 0.01, 0.001]} grid GridSearchCV( SVC(kernelrbf, class_weightbalanced), param_grid, cv3, scoringf1_macro, n_jobs-1 ) grid.fit(X_train, train[label]) print(grid.best_params_, grid.best_score_)class_weightbalanced让类别不平衡时自动加权比手动过采样省事。scoring用f1_macro而不是准确率因为准确率在类别不平衡时会骗人。n_jobs-1用满 CPU 核网格搜索能快好几倍。跑完看best_params_如果最优C落在边界值上比如你设的最大值是 10结果选了 10说明搜索范围该往外扩。3.3 模型持久化与推理joblib 存取的注意点训练好的模型要存下来给 GUI 调用。sklearn 模型用 joblib 比 pickle 快尤其是带 numpy 数组的。存的时候要把 TF-IDF 向量器和 SVM 模型一起存推理时先 transform 再 predict顺序不能反。import joblib # 向量器和模型必须一起保存 joblib.dump(tfidf, tfidf.pkl) joblib.dump(svm_linear, svm_model.pkl) # 推理时加载 tfidf_loaded joblib.load(tfidf.pkl) svm_loaded joblib.load(svm_model.pkl) def predict_svm(text): tokens cut_text(text) vec tfidf_loaded.transform([tokens]) return svm_loaded.predict(vec)[0] print(predict_svm(质量很好物流也快))注意cut_text函数必须和训练时完全一致包括词典和停用词表。如果训练时用了自定义词典推理时没加载分词结果就会漂移预测自然不准。这个坑我在实际项目里见过不止一次模型离线评估 0.9上线后掉到 0.6查了半天是分词不一致。4. LSTM 路线捕捉词序与长距离依赖4.1 从词向量到 LSTM为什么需要序列建模SVM 把评论当成词袋丢掉了词序。「我不觉得好」和「我觉得不好」在词袋里几乎一样但情感完全不同。LSTM 通过门控机制记住前面的信息遗忘门决定丢什么输入门决定存什么输出门决定当前输出什么。这种结构让它能捕捉「不」和后面形容词的搭配关系对否定句、转折句更稳。LSTM 的输入是词向量序列。词向量可以用预训练的比如 Word2Vec、GloVe 中文版也可以在自己的语料上训一个 Embedding 层。数据量小的时候预训练词向量能显著提升效果数据量大且领域特殊时自己训也行。我一般先用预训练词向量冻结 Embedding 层跑一版再解冻微调。4.2 Keras 搭 LSTM 情感分类器的完整代码下面是一个能直接跑的三层结构Embedding 层把词索引转成向量LSTM 层提取序列特征全连接层输出二分类概率。maxlen是评论截断长度超过的截掉不足的补零。import numpy as np from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout MAXLEN 100 # 评论平均长度 50 左右100 够覆盖大多数 VOCAB_SIZE 20000 tokenizer Tokenizer(num_wordsVOCAB_SIZE, oov_tokenOOV) tokenizer.fit_on_texts(train[tokens]) def to_seq(texts): seqs tokenizer.texts_to_sequences(texts) return pad_sequences(seqs, maxlenMAXLEN, paddingpost, truncatingpost) X_train_seq to_seq(train[tokens]) X_val_seq to_seq(val[tokens]) model Sequential([ Embedding(VOCAB_SIZE, 128, input_lengthMAXLEN), LSTM(64, dropout0.3, recurrent_dropout0.3), Dense(32, activationrelu), Dropout(0.3), Dense(1, activationsigmoid) # 二分类用 sigmoid ]) model.compile(lossbinary_crossentropy, optimizeradam, metrics[accuracy]) model.summary() history model.fit( X_train_seq, train[label], validation_data(X_val_seq, val[label]), epochs10, batch_size64 )Embedding的 128 是词向量维度评论任务 64 到 256 都常见。LSTM(64)表示隐藏单元数太大容易过拟合太小记不住。dropout和recurrent_dropout分别作用在输入和循环连接上是防过拟合的主要手段。paddingpost在末尾补零配合truncatingpost截断保证所有序列等长。如果标签是三类正/中/负最后一层改成Dense(3, activationsoftmax)损失换成categorical_crossentropy。4.3 训练曲线怎么看过拟合与欠拟合的识别history里存了每轮的训练和验证指标画出来一眼就能判断状态。训练 loss 一直降、验证 loss 先降后升是过拟合该加 dropout 或减层数两条曲线都高且不降是欠拟合该加容量或调学习率。我一般会写个小函数把曲线画出来比盯着数字直观。import matplotlib.pyplot as plt def plot_history(history): fig, (ax1, ax2) plt.subplots(1, 2, figsize(12, 4)) ax1.plot(history.history[loss], labeltrain) ax1.plot(history.history[val_loss], labelval) ax1.set_title(Loss); ax1.legend() ax2.plot(history.history[accuracy], labeltrain) ax2.plot(history.history[val_accuracy], labelval) ax2.set_title(Accuracy); ax2.legend() plt.show() plot_history(history)验证 loss 最低的那一轮才是最优模型不是最后一轮。Keras 的ModelCheckpoint回调可以自动保存验证 loss 最低的权重省得手动挑。from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping callbacks [ ModelCheckpoint(lstm_best.h5, monitorval_loss, save_best_onlyTrue), EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue) ] model.fit(X_train_seq, train[label], validation_data(X_val_seq, val[label]), epochs20, batch_size64, callbackscallbacks)patience3表示验证 loss 连续 3 轮不降就停restore_best_weights自动恢复到最优轮。这两个回调基本是标配能省不少调参时间。5. GUI 集成与避坑从模型到能点的界面5.1 Tkinter 做一个能输入评论、显示结果的界面毕设项目通常要求 GUITkinter 是 Python 自带库不用额外装。界面逻辑很简单一个文本框输入评论一个按钮触发预测一个标签显示结果。关键是把前面存好的模型加载进来预测函数和训练时保持一致。import tkinter as tk import joblib tfidf joblib.load(tfidf.pkl) svm joblib.load(svm_model.pkl) def on_predict(): text entry.get(1.0, end).strip() if not text: result_label.config(text请输入评论) return tokens cut_text(text) vec tfidf.transform([tokens]) pred svm.predict(vec)[0] result_label.config(text正面 if pred 1 else 负面) root tk.Tk() root.title(商品评论情感分析) entry tk.Text(root, height5, width40) entry.pack() tk.Button(root, text分析, commandon_predict).pack() result_label tk.Label(root, text等待输入, font(Arial, 14)) result_label.pack() root.mainloop()entry.get(1.0, end)里的1.0表示第一行第零列end表示末尾这是 Tkinter 文本框的固定写法。预测函数里调用的cut_text必须和训练时是同一个函数最好抽到一个公共模块里 import别在两处各写一份。5.2 避坑与排查五条真实踩坑记录现象一离线准确率 0.9GUI 里输入同样的话却预测错。原因训练和推理的分词不一致或者 TF-IDF 用了不同的fit数据。解决把分词和向量化封装成函数训练和推理共用向量器只fit一次并保存。现象二LSTM 训练 loss 不降一直在 0.69 附近。原因标签没做数值化或者Embedding的input_length和实际序列长度不匹配。解决检查标签是不是 0/1 整数pad_sequences的maxlen和Embedding的input_length是否一致。现象三SVM 训练报「含 NaN 或无穷大」。原因TF-IDF 矩阵里有空行某条评论分词后为空。解决分词后过滤掉长度为 0 的样本或在TfidfVectorizer里设min_df1并检查空文本。现象四模型文件几百 MB加载慢。原因LSTM 模型保存了优化器状态。解决推理时用model.save_weights只存权重或者加载时compileFalse。现象五GUI 点击按钮卡死。原因模型加载或预测在主线程里跑耗时操作阻塞了界面。解决把模型加载放到程序启动时一次性完成预测本身很快不会卡如果模型特别大用线程池异步处理。5.3 两套模型的对比与选型建议维度SVM TF-IDFLSTM 词向量训练数据量千级即可万级以上更稳训练时间秒级分钟到小时级词序建模无有否定句处理依赖 n-gram天然捕捉可解释性支持向量可查黑匣子部署体积几 MB几十到几百 MB适合场景快速基线、小样本精度优先、数据充足我的建议是两条都跑用同一份测试集比 F1。如果 LSTM 只比 SVM 高 1 到 2 个点而部署成本高很多就选 SVM。如果差 5 个点以上或者业务里否定句、长评论占比高再上 LSTM。6. 让模型真正可用的三个进阶技巧第一个技巧是阈值调优。二分类模型默认阈值 0.5但业务上「宁可错杀不可放过」和「宁可放过不可错杀」对应的阈值完全不同。用验证集画 P-R 曲线找到 F1 最高的阈值存下来给推理用。这一步能让 F1 再涨 1 到 3 个点成本几乎为零。from sklearn.metrics import precision_recall_curve proba svm_linear.decision_function(X_val) precision, recall, thresholds precision_recall_curve(val[label], proba) f1 2 * precision * recall / (precision recall 1e-8) best_threshold thresholds[f1.argmax()] print(最佳阈值:, best_threshold)第二个技巧是错误分析。把验证集里预测错的样本捞出来人工看几十条往往能发现系统性问题是不是某个品类词没进词典是不是反讽句全错是不是标签本身有噪声。改词典、加规则、清洗标签比调模型参数见效快得多。第三个技巧是模型融合。SVM 和 LSTM 的预测结果做加权平均权重用验证集 F1 来定。两套模型错的地方往往不一样融合后能互补。实现上就是把两个模型的概率输出按权重相加再取 argmax。svm_proba svm_linear.decision_function(X_val) lstm_proba model.predict(X_val_seq).ravel() # 权重按验证集表现分配这里假设各 0.5 ensemble 0.5 * svm_proba 0.5 * lstm_proba pred (ensemble best_threshold).astype(int)注意 SVM 的decision_function输出范围是负无穷到正无穷LSTM 的 sigmoid 输出是 0 到 1融合前最好都归一化到同一量纲否则权重要重新标定。这套方案我从数据清洗一路做到 GUI最大的体会是模型精度的大头不在网络结构而在数据质量和分词一致性。我现在的习惯是每接一个新数据集先花半天做错误分析和词典维护再动模型。希望帮到你。本文还有配套的精品资源点击获取