我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

商品评论情感分析:从数据预处理到LSTM模型构建全流程

商品评论情感分析:从数据预处理到LSTM模型构建全流程 简介一套面向毕业设计场景的商品评论情感分析项目资料基于机器学习与Python构建涵盖情感分析从数据获取、文本预处理、词向量训练到模型搭建与界面展示的完整流程。适用于计算机、人工智能、电子信息等专业的在校生完成毕业论文或课程设计也适合开发者以此项目深入理解自然语言处理与机器学习整合应用。压缩包共44个文件主要包含可执行的Python程序、CSV与Excel格式的数据集、训练好的模型权重文件、配置说明和项目文档等可支撑直接运行、复现实验和二次扩展压缩包大小约66.66MB整体结构按爬虫、预处理、模型、界面等模块划分便于按需查找。目前已有47人学习下载项目代码经实际运行测试通过并获导师指导认可、答辩评审分达到95分除作为毕业设计完整交付物外还可基于现有框架改进算法、增加细粒度情感维度功能性与完整性兼具。1. 商品评论情感分析这是一条能直接跑通的 NLP 毕设流水线拿到这份“基于机器学习的商品评论情感分析”项目时我最直观的感受是它不是那种只丢给你一个 train.py 就跑路的半成品而是把数据、特征、模型、爬虫、GUI 全部串成了一条完整的链路。做商品评论情感分析真正让人头大的不是“用哪个模型”而是从原始评论数据到能喂进模型的向量中间那段又臭又长的预处理管道——这份资料恰好把这段路完整走了一遍。它适合要做毕设、课设或者想从零跟一遍中文 NLP 分类流程的同学不光是看代码还能看到一份高分项目该有的完整度和工程组织方式。2. 数据预处理从 pos.xls、neg.xls 到 train_cut.csv 的清洗与分词链路2.1 原始数据格式与分类标注打开资源里的数据文件可以看到三类原始数据pos.xls、neg.xls和neutral.csv分别对应正面、负面和中性评论。pos.xls和neg.xls是 Excel 格式里面存的是用户对商品的文字评价neutral.csv是 CSV 格式用于扩充中性样本。还有一个test_set.csv是后续测试模型用的独立样本集。文件格式内容用途pos.xlsExcel正面商品评论训练集正样本neg.xlsExcel负面商品评论训练集负样本neutral.csvCSV中性商品评论训练集中性样本test_set.csvCSV混合评论测试集train_cut.csvCSV分词后带标签语料特征工程输入这三类数据合成三分类任务为后续打标签做准备。注意原始文本是未分词的完整句子Excel 文件里可能掺杂着换行、HTML 片段和 URL这些都会直接影响后续特征质量需要统一清洗。2.2 预处理脚本 review_pretreatment.py 的清洗、分词与打标逻辑review_treatment/review_pretreatment.py是整条数据管线的起点。它做四件事读原始文件、清洗文本、分词、打标签并合并输出。用 Python 代码展开这个流程大概是下面这样import pandas as pd import jieba import re def clean_text(text): # 去掉 HTML 标签评论里经常混入网页片段 text re.sub(r.*?, , str(text)) # 去掉 URL text re.sub(rhttps?://\S, , text) # 去掉多余空白 text re.sub(r\s, , text).strip() return text # 读取正面评价engine 指定为 xlrd 处理 .xls 老格式 pos_df pd.read_excel(pos.xls, enginexlrd, headerNone, names[comment]) pos_df[label] 1 # 正面标签记为 1 # 读取负面评价 neg_df pd.read_excel(neg.xls, enginexlrd, headerNone, names[comment]) neg_df[label] 0 # 负面标签记为 0 # 读取中性评价 neu_df pd.read_csv(neutral.csv, encodingutf-8) neu_df[label] 2 # 中性标签记为 2 def cut_and_filter(text): # 分词后过滤停用词和单字 words [w for w in jieba.cut(text) if w.strip() and len(w) 1] return .join(words) # 合并、清洗、分词 all_df pd.concat([pos_df, neg_df, neu_df], ignore_indexTrue) all_df[cut_comment] all_df[comment].apply(clean_text).apply(cut_and_filter) # 打乱顺序防止同类别样本扎堆 all_df all_df.sample(frac1, random_state42) all_df.to_csv(train_cut.csv, indexFalse, encodingutf-8)代码里的enginexlrd是踩过坑的人才写得出来的细节。pandas 读取.xls老格式时新版 xlrd 2.x 只支持.xlsx不指定引擎的话大概率直接抛异常。清洗阶段我把评论里的 HTML 标签和 URL 全部剥掉因为它们对情感判断没有贡献反而会让 word2vec 学出奇怪的词向量。分词阶段过滤掉了长度为 1 的词中文里大量单字在情感分析里是噪音。标签定义值得注意正面为 1负面为 0中性为 2。这个标号方案在后面对应了 SVM 和 LSTM 的输出层设计——SVM 用多分类策略LSTM 如果是categorical_crossentropy就要用 3 个输出节点。有一类常见的错误做法是把中性评论丢到测试集里不管或者干脆删掉结果是模型在实际预测时对中性评论特别容易翻车。2.3 分词的边界为什么这套方案在小样本评论数据上够用train_cut.csv最终的行数取决于原始语料规模。对这个毕设体量的项目训练语料通常在几千条量级jieba 完全够用不需要上 BERT 的分词器。分词的质量对后续 Word2Vec 影响很大尤其是评价语里常见的叠词和口语化表达比如“非常非常好”“物流贼快”这类词jieba 可能会拆出“非常”“非常”“好”这种重复不是冗余反而是情感强度的信号。所以我一般不会做严格的去重只做停用词过滤。预处理输出的文件里有两个关键字段cut_comment和label。cut_comment的格式是空格分隔的词序列这正好是后面 Word2Vec 训练需要的输入格式label是整数标签。这个文件也是一份独立的数据资产哪怕后续完全换一个模型只要把train_cut.csv重新读进来就可以再造特征不必回头再碰原始 Excel。预处理这条链路最容易被忽略的是编码问题。资源里的负样本 CSV 并不是 UTF-8如果脚本里不设置encodinggbk或errorsignore读出来的内容就是一堆乱码甚至直接报 UnicodeDecodeError。这个问题在后面的避坑章节里会单独展开。3. 特征工程与机器学习基线Word2Vec 词向量与 SVM 分类器的配合3.1 为什么不用 TF-IDF 而选 Word2Vec商品评论本质上是一段短文本TF-IDF 会把每条评论表示成一个长度等于词表大小的稀疏向量维度上千甚至上万而且词与词之间的关系完全丢失——“好吃”和“美味”在 TF-IDF 里是两列完全不相关的特征。而 Word2Vec 把每个词映射成稠密向量语义相近的词在向量空间里距离更近。这就是这份项目选择 Word2Vec 的核心原因评论里的情感表达方式太多样了用稀疏特征很难覆盖同义改写的情况。Word2Vec 训练完成后句子向量怎么获得常见做法是取句子内所有词向量的平均值。这个操作虽然简单但在短文本情感分析上是性价比最高的特征表达方式。不用 TF-IDF 加权平均是因为评论本身就是短文本高频词往往才是真正的评价主体TF-IDF 加权反而会把“这个”“那个”这类词权重压低把低频但情感明确的词权重抬上来结果反而偏离了评论者的真实意图。算平均的代码很简洁import numpy as np def sentence_vector(words, w2v_model, vec_dim100): vectors [] for w in words: if w in w2v_model.wv: vectors.append(w2v_model.wv[w]) # 句子中全部词都不在词表里时返回零向量 if len(vectors) 0: return np.zeros(vec_dim) return np.mean(vectors, axis0)参数vec_dim100对应的是 Word2Vec 训练时的vector_size100两者不一致会导致后续 SVM 训练时维度报错。这个函数里我特意把“词不在词表”的情况兜底成了零向量因为评论里总会有错别字或者未登录词直接跳过会让句子向量丢失语义主心骨。3.2 训练词向量comment_text.model 与 w2v_model.pkl 的关系资源里有两个词向量相关文件comment_text.model和w2v_model.pkl。前者是 gensimWord2Vec训练后直接保存的原生模型里面包含了词表、向量和训练参数后者是把同一份模型用pickle或者 joblib 重新序列化后的结果目的是方便在 SVM 训练脚本里快速加载。两者内容等价区别只是加载方式。from gensim.models import Word2Vec # 加载训练语料 sentences [] with open(train_cut.csv, encodingutf-8) as f: for line in f: # 假设第二列是分词后的评论文本 parts line.strip().split(,) if len(parts) 1: sentences.append(parts[1].split()) # 训练词向量这里是最核心的三个参数 w2v_model Word2Vec( sentences, vector_size100, # 向量维度太小语义表达不够太大训练慢且容易过拟合 window5, # 窗口大小评论里情感词常与程度副词相邻 min_count1, # 出现次数少于这个值的词会被丢弃 workers4, # 并行线程数取决于 CPU sg0, # 0 表示 CBOW小样本上比 skip-gram 更稳 epochs20 # 训练轮次评论语料小多跑几轮能收敛得更好 ) w2v_model.save(comment_text.model) # 保存词向量文本格式方便后续直接读取 w2v_model.wv.save_word2vec_format(comment_text.vector)min_count1值得展开说。正规项目里这个词一般设成 5 或 10用来过滤低频噪音词。但这个毕设项目的语料量有限词频本来就低如果设成 5很多情感词直接就被过滤掉了导致句子向量大量落到零向量上。sg0选择 CBOW 也是同样的考虑CBOW 在语料不足时比 skip-gram 更能平滑估计词义。3.3 句子向量化与 train_svm.py 训练细节train_svm.py做的事情就是把train_cut.csv里的每条评论通过sentence_vector变成 100 维向量然后丢进 sklearn 的SVC训练。这里的 SVM 不是最终杀手锏而是一个强基线——它用来证明“用向量平均线性分类器就能达到 XX 准确率”后面 LSTM 才有对照价值。from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from gensim.models import Word2Vec w2v_model Word2Vec.load(comment_text.model) X, y [], [] with open(train_cut.csv, encodingutf-8) as f: for line in f: parts line.strip().split(,) X.append(sentence_vector(parts[1].split(), w2v_model)) y.append(int(parts[0])) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) svm SVC(kernellinear, C1.0, class_weightbalanced) svm.fit(X_train, y_train) print(classification_report(y_test, svm.predict(X_test)))kernellinear是短文本分类的默认选择。RBF 核在样本量小的情况下极其容易过拟合测试集准确率漂移得很厉害线性核在这里不仅训练快而且可解释性好——从分类报告里看每个类别的 precision 和 recall能快速定位是哪一类评论被混淆。class_weightbalanced解决的是样本不均衡问题如果原始数据里中性评论明显少于正负评论这个参数会让小类别的误判代价变高。SVM 在这个项目里的角色是“基线对照”。毕设答辩时老师大概率会问“为什么不用神经网络就行还搭一个 SVM”答案是 SVM 用一个线性分类器在同样的特征上就能给出可直接对比的准确率LSTM 如果不如 SVM那说明是特征或调参出了问题而不是模型结构就一定优越。4. 深度学习路线用 Keras 训练 LSTM 情感分类模型的完整过程4.1 情感分类任务里 LSTM 到底学到了什么如果说 SVM 的特征是“句子向量的平均”那 LSTM 的输入是“每个词的向量序列”。评论里的情感表达是有顺序的比如“好吃但量少”——前半段是正面后半段是负面整个句子的情感倾向是复杂的。词向量平均会把这两个信号中和成一个向量而 LSTM 通过门控机制按顺序处理每个词保留住“先扬后抑”这种词序信息。这就是同项目里放 SVM 又放 LSTM 的学术意义LSTM 不是简单地换一个分类器而是换了一种文本建模方式。资源里的train_lstm.py是一个完整的训练脚本从读取语料到保存模型一次性完成。我拆解的常见结构是这样from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences import pandas as pd # 加载预处理后的语料 df pd.read_csv(train_cut.csv, encodingutf-8) # Tokenizer 负责把词序列映射成整数索引 tokenizer Tokenizer() tokenizer.fit_on_texts(df[cut_comment]) sequences tokenizer.texts_to_sequences(df[cut_comment]) # 评论长度差异很大必须 padding 到同一长度 max_len 50 X pad_sequences(sequences, maxlenmax_len, paddingpost, truncatingpost) y pd.get_dummies(df[label]).values # 3 分类转 one-hot vocab_size len(tokenizer.word_index) 1 model Sequential() model.add(Embedding(input_dimvocab_size, output_dim100, mask_zeroTrue)) model.add(LSTM(128, return_sequencesFalse)) model.add(Dropout(0.5)) model.add(Dense(3, activationsoftmax)) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) model.summary() # 分出一部分验证集每个 epoch 结束后能看到过拟合苗头 history model.fit(X, y, epochs30, batch_size32, validation_split0.2, verbose1) model.save(lstm_new.h5)pad_sequences里的paddingpost我遇到过坑默认是前向补零但这会破坏模型对位置的感知在 Embedding 层设了mask_zeroTrue的情况下后向补零能保证零向量不会参与 attention 或 LSTM 状态更新语义上更合理。max_len50是我根据商品评论长度分布选的折中值——大部分评论在 20 到 40 个词之间设太大训练慢设太小长评论会被截断。LSTM 中间层用 128 个记忆单元这是一个经验值。评论这种短文本不需要很大的隐藏层维度128 已经能捕捉词序间的组合关系。Dropout(0.5)放在 LSTM 输出之后是防止它在 30 个 epoch 里直接背下训练集。Dense 层用 3 个节点对应三类标签激活函数是 softmax所以损失函数必须配categorical_crossentropy如果标签是整数形式就得换sparse_categorical_crossentropy这是新手最容易搞混的搭配。4.2 train_lstm.py 的网络结构与训练参数刚才的代码用的是快速搭建的 Sequential 结构。在train_lstm.py里可能还会涉及早期停止或者学习率调度这些不是必需的但对最终准确率有明显帮助。我通常会在fit里加入from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ) history model.fit( X, y, epochs30, batch_size32, validation_split0.2, callbacks[early_stop], verbose1 )patience5的含义是连续 5 个 epoch 验证集损失不再下降就停止。对毕设项目来说30 个 epoch 足够长但如果不加早停模型大概率在第 15 到 20 个 epoch 后开始过拟合训练集准确率逼近 99%验证集却一路下滑。资源里能看到lstm_new.h5和lstm_three.h5两个模型文件说明训练者曾经多次调整结构重新训练这对最终 95 分的答辩成绩是很关键的一手实验记录。4.3 模型保存与加载lstm_three.h5、lstm_new.h5 和 lstm.yml 的区别lstm_three.h5保存的可能是一个三层 LSTM 堆叠的实验版本——三层结构的表达能力强但参数量大在几千条评论的小语料上极易过拟合除非配合很重的 dropout否则效果反而不如单层。lstm_new.h5是调整后的最终版本对应单层 LSTMDropout 结构。lstm.yml是模型结构的 YAML 描述文件里面记录了每一层的类型、神经元数和激活函数。如果你拿到这批文件想混合使用要注意.h5文件里同时包含结构和权重直接load_model就能用lstm.yml只是一个结构描述需要配合model_from_yaml再手动加载权重才能完成恢复。加载后的预测代码from tensorflow.keras.models import load_model import numpy as np model load_model(lstm_new.h5) # 假设 tokenizer 已经从训练阶段保存 text 这个手机电池太不耐用了半天就没电 words .join([w for w in jieba.cut(text) if len(w) 1]) seq tokenizer.texts_to_sequences([words]) seq pad_sequences(seq, maxlen50, paddingpost, truncatingpost) # pred 是三个类别的概率分布 pred model.predict(seq)[0] label np.argmax(pred)np.argmax(pred)把概率最大的下标映射回标签也就是 0、1、2 三类。这里有一个易错点如果训练时标签是正面 1、负面 0、中性 2那 argmax 的结果也要对齐到这个映射表不能想当然认为 0 就是负面。这个对齐错误我见过不止一次尤其在把别人的模型接入自己数据时。5. 避坑记录编码、内存、过拟合和 chromedriver 的五个真实案例5.1 Excel 读取直接报错现象pd.read_excel(pos.xls)运行时抛异常提示xlrd.biffh.XLRDError: Excel xls file; not supported。原因xlrd 从 2.0 版本起移除了对 xls 格式的支持而 pandas 在未指定引擎时会默认使用 xlrd。这个项目 2021 年后创建依赖环境里很可能装了新版本 xlrd。解决把读取语句改成pd.read_excel(pos.xls, enginexlrd)同时安装旧版依赖pip install xlrd1.2.0。这个坑的根因不是代码逻辑错误而是环境版本漂移我一般会在虚拟环境里固定依赖版本避免下次跑的时候静默升级。5.2 分词结果全是空行现象train_cut.csv的cut_comment列大面积是空字符串每行只剩一个逗号和标签。原因清洗函数clean_text把原始文本过滤得太狠比如去掉 HTML 后又执行了空白压缩但原始评论里大量内容确实只有 HTML 而无正文另一个可能是读入 Excel 时 encoding 没对上pandas 默认读成无效字符后续正则全被剥掉。解决在clean_text里加一个判断清洗后如果字符串长度小于 2 就直接标记为无效样本并丢弃同时在读取 xls 时确认列名是否与预期一致——有时 Excel 第一行是表头headerNone会错误地把表头当成数据。5.3 训练 LSTM 时内存直接耗尽现象model.fit刚开始跑内存占用持续飙升最后进程被杀。原因Tokenizer没有设置num_words上限词表大小等于语料里全部词的个数。如果语料有几万条评论词表轻松到几万甚至十几万Embedding 层input_dimvocab_size会把参数量撑到几百万再加上 padding 后的序列矩阵内存直接爆掉。解决给Tokenizer加上num_words5000只保留出现频率最高的 5000 个词同时max_len控制为 50batch_size调到 32。这样一来参数总量降了几个数量级准确率不会有明显损失因为低频词的向量本来就学不充分。5.4 模型预测始终输出同一个类别现象测试集上准确率看着还可以但拿真实评论去预测无论说什么都返回“正面”或者都返回“中性”。原因两类问题最常见——样本不均衡和阈值漂移。数据集里正面评论数量远大于负面和中性模型学会把所有样本都判成多数类在测试集上因为同样分布所以准确率不低另一个原因是 LSTM 的Dense(3, activationsoftmax)输出是概率分布直接 argmax 可能过度自信边界样本全部落到了多数类侧。解决确认class_weight是否启用了LSTM 对应的方案则是改造 Dense 层输出为单个 sigmoid 节点把任务从三分类改成正负二分类中性评论归入负面或干脆去掉。对三分类我一般人为调整 argmax 的阈值比如只有概率超过 0.6 才输出对应类别否则输出中性。5.5 chromedriver 启动失败爬虫跑不起来现象运行review_crawler/restaurant_crawler.py时 Selenium 报错WebDriverException: unknown error: cannot find Chrome binary或者 Chrome 启动后立刻崩溃。原因chromedriver 版本和本机 Chrome 主版本不匹配。这是最常见的环境问题项目里的 chromedriver 是作者当时下载的后来 Chrome 自动更新到了新版本老驱动就失效了。解决进设置查看本机 Chrome 的版本号然后去对应站点下载匹配版本的 chromedriver替换资源里的同名文件。这个坑与项目代码无关但几乎所有人都会遇到我习惯在启动爬虫脚本前加一个版本检查driver webdriver.Chrome()包在 try 里捕获异常后提示用户更新驱动而不是抛一整屏晦涩堆栈。6. 进阶把模型接进 GUI 并用一句话快速验证模型文件6.1 main_page.py 的界面与模型调用逻辑main_page.py是整个项目的对外窗口。它的逻辑不复杂左侧是输入框右侧是结果展示用户粘贴一段评论后点击按钮触发预处理和模型预测然后把结果更新到界面。实现上会同时加载model.pklSVM和lstm_new.h5LSTM用同一个输入跑两个模型显示各自的分类结果和概率置信度。这种“双模型对照”的展示方式在答辩时很加分评审老师一眼就能看到两种技术路线的差异。import tkinter as tk from tkinter import scrolledtext from tensorflow.keras.models import load_model import joblib class SentimentApp: def __init__(self): self.root tk.Tk() self.root.title(商品评论情感分析) self.lstm_model load_model(lstm_new.h5) self.svm_model joblib.load(model.pkl) # 省略界面布局代码…… def predict_sentiment(self, text): # 对文本做同样的预处理然后分别调用两个模型 lstm_result self.lstm_model.predict(text_sequence) svm_result self.svm_model.predict(text_vector) return lstm_result, svm_result这里有一个细节容易被忽略SVM 模型在训练时用的是 Word2Vec 平均向量model.pkl里只保存了分类器参数词向量模型需要单独加载而 LSTM 模型的 tokenizer 也必须从训练状态恢复。如果 GUI 脚本里没有保留 tokenizer 对象简单粗暴的做法是在训练脚本里把 tokenizerpickle保存然后在 GUI 启动时加载进来。缺少这一步的话界面能打开但一预测就报错。6.2 快速验证模型文件是否损坏的技巧每次拿到.h5、.pkl、.model这类模型文件第一件事不是急着加载而是做一个十秒钟的冒烟测试。对一个随机输入跑一次 predict确认输入输出维度匹配、不抛异常、结果是一个合法的概率分布。我把这个动作固化成了一个小脚本import numpy as np from tensorflow.keras.models import load_model model load_model(lstm_new.h5) fake_input np.zeros((1, 50)) # 假设 max_len50 try: out model.predict(fake_input) print(output shape:, out.shape) print(prob sum:, out.sum(axis1)) except Exception as e: print(模型加载异常:, e)如果这个脚本能跑通说明模型文件至少格式正确、能加载、能计算接下来才值得花时间去接业务数据。这个习惯帮我避开过至少两次“辛辛苦苦接到项目里却因为模型文件损坏而全部重来”的情况。对这份项目我最后想补一句实际操作的体会资料里的模型文件是作者在特定数据分布和预处理参数下训练出来的如果你的数据格式、分词方式、停用词表跟原作者不一致直接调用会得到偏低的准确率。最稳的做法是把train_cut.csv重新跑一遍预处理再在它之上重新训练自己的模型。从那以后我每次接别人的毕设项目都强制自己先走一遍完整的数据管线而不是直接跳去调模型。希望帮到你。本文还有配套的精品资源点击获取
返回列表