简介:这份资源是面向计算机相关专业学生与Python实战学习者的深度学习项目包,以LSTM为核心模型完成电商购物评论的情感分析任务,可直接用于毕业设计、课程设计或期末大作业。项目围绕京东商城购物评论展开,涵盖数据采集、中文分词与停用词处理、模型训练与预测等完整流程,并附带已训练好的模型文件,便于快速复现与二次开发。压缩包共39个文件,约164.29MB,包含8个Python源码文件、6组TensorFlow模型权重与索引文件、1个checkpoint与model文件、若干png结果图及txt说明文档,结构清晰、模块分明。目前已有404人学习下载,适合需要完整赛题方案、可运行代码与排错参考的读者,帮助理解LSTM在文本情感分类中的落地方式与工程组织思路。
1. 电商评论情感分析:LSTM 为什么仍是毕业设计里最稳的基线
电商评论每天以百万级增长,运营团队想从「好评如潮」里挖出退货隐患,靠人工翻页根本不现实。基于深度学习(LSTM)的电商购物情感分析,本质是把一条中文评论映射成「正面 / 负面」的概率分布,让机器替人读完所有评价。这个方向之所以在毕业设计里长盛不衰,是因为它同时满足三个条件:数据公开易得、模型结构清晰、指标可量化。LSTM 擅长处理变长序列,能记住「虽然发货慢,但质量真的好」这种转折句里的关键情感词,比词袋模型和朴素贝叶斯高出一截。适合谁做?计算机、大数据、电商运营方向的本科生,只要会用 Python 和 PyTorch,两周内能跑通全流程。下面我把从数据清洗到模型部署的完整路径拆开讲,参数和踩坑点都会给到。
2. 数据从哪来、怎么洗:电商评论的获取与预处理
2.1 公开数据集与自建语料的取舍
做电商购物情感分析,第一步不是写模型,而是确认标签质量。常见做法是先用公开中文评论数据集打底,比如 ChnSentiCorp(酒店/电商混合)或 online_shopping_10_cats(10 个品类、约 6 万条),这些数据在 GitHub 上能直接搜到,标签只有 0/1 两类,适合快速验证。如果导师要求「真实电商场景」,那就自己爬某平台商品评论,但要注意:爬下来的原始数据没有标签,需要人工标注 500~1000 条作为测试集,训练集可以用公开数据 + 弱监督(比如用星级映射:4~5 星为正面,1~2 星为负面,3 星丢弃)。我一般会保留 10% 的公开数据做验证集,自建数据只做最终测试,这样指标不会虚高。
提示:别用 3 星评论,情感极性模糊,强行标注会引入噪声,LSTM 会学偏。
2.2 中文分词与停用词处理的代码实现
中文不像英文有空格,必须先分词。jieba 是最稳的选择,加载自定义词典能避免「退货」被切成「退/货」。下面这段代码完成读取、分词、去停用词、截断/填充到固定长度。
import jieba import pandas as pd import re from tensorflow.keras.preprocessing.sequence import pad_sequences # 加载停用词表(哈工大停用词表 + 电商领域补充) stopwords = set() with open('stopwords.txt', 'r', encoding='utf-8') as f: for line in f: stopwords.add(line.strip()) # 补充电商高频无意义词 stopwords.update(['包邮', '宝贝', '亲', '卖家', '物流', '快递']) def clean_text(text): # 去除非中文、数字、字母的符号 text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', str(text)) # 分词 words = jieba.lcut(text) # 去停用词和单字 words = [w for w in words if w not in stopwords and len(w) > 1] return words # 假设 df 有 'review' 和 'label' 两列 df = pd.read_csv('ecommerce_reviews.csv') df['cut'] = df['review'].apply(clean_text) # 构建词表:只保留出现次数 >= 3 的词 from collections import Counter all_words = [w for words in df['cut'] for w in words] word_freq = Counter(all_words) vocab = {w: i+1 for i, (w, c) in enumerate(word_freq.items()) if c >= 3} # 0 留给 padding # 转成索引序列 df['seq'] = df['cut'].apply(lambda x: [vocab.get(w, 0) for w in x]) # 统一长度 128,不足补 0,超出截断 MAX_LEN = 128 X = pad_sequences(df['seq'].tolist(), maxlen=MAX_LEN, padding='post', truncating='post') y = df['label'].values逻辑说明:clean_text先过滤噪声字符,再分词去停用词,保留长度大于 1 的词能去掉「的」「了」这类无意义单字。词表只保留频次 ≥ 3 的词,低频词直接映射为 0(padding 占位),这样能减少嵌入层参数量。MAX_LEN=128覆盖了 95% 的电商评论长度,超过的截断,不足的补零。参数怎么改?如果评论普遍短(如生鲜类),可以降到 64;如果长评多(如家电),升到 256,但训练时间会线性增加。
2.3 标签平衡与数据集划分的注意点
电商评论天然不平衡,正面往往占 80% 以上。直接训练会让模型全预测正面,准确率虚高到 0.8 但 F1 只有 0.1。我一般用两种手段:一是对负面样本过采样(复制到与正面同量),二是在损失函数里加class_weight。划分比例按 8:1:1 走,训练集、验证集、测试集都要保证正负比例一致。用sklearn的train_test_split时加stratify=y就能分层抽样。
from sklearn.model_selection import train_test_split X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42) X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, stratify=y_temp, random_state=42)stratify=y是关键,不加的话可能验证集里全是正面,早停会失效。random_state固定后结果可复现,毕业设计答辩时能经得起追问。
3. LSTM 模型搭起来:嵌入层、双向结构、Dropout 怎么配
3.1 为什么用双向 LSTM 而不是单向
单向 LSTM 只能从左到右读,遇到「质量不错,但是物流太慢」这种句子,读到「但是」时已经忘了前面的「质量不错」,情感判断会偏向负面。双向 LSTM 同时跑正向和反向,把两个方向的最后隐状态拼接,能同时捕捉前后文。电商评论里转折句占比不低,双向结构通常比单向高 2~3 个百分点的 F1。代价是参数量翻倍,训练慢一点,但毕业设计的规模完全扛得住。
3.2 完整模型代码与参数解释
下面用 PyTorch 实现一个 Embedding + BiLSTM + 全连接层的分类器。嵌入层维度 128,LSTM 隐藏层 64,双向拼接后 128 维,最后接 Dropout 和线性层。
import torch import torch.nn as nn class BiLSTMClassifier(nn.Module): def __init__(self, vocab_size, embed_dim=128, hidden_dim=64, num_layers=1, dropout=0.5): super().__init__() # padding_idx=0 表示索引 0 不参与梯度更新 self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers=num_layers, bidirectional=True, batch_first=True, dropout=dropout if num_layers > 1 else 0) self.dropout = nn.Dropout(dropout) self.fc = nn.Linear(hidden_dim * 2, 1) # 双向拼接 self.sigmoid = nn.Sigmoid() def forward(self, x): # x: (batch, seq_len) embedded = self.embedding(x) # (batch, seq_len, embed_dim) lstm_out, (hidden, cell) = self.lstm(embedded) # hidden: (num_layers*2, batch, hidden_dim) -> 取最后一层正向和反向 hidden = torch.cat((hidden[-2], hidden[-1]), dim=1) # (batch, hidden_dim*2) out = self.dropout(hidden) out = self.fc(out) return self.sigmoid(out).squeeze(1)参数说明:embed_dim=128是中文评论的常用值,太小表达不足,太大容易过拟合;hidden_dim=64配合双向得到 128 维表示,与嵌入维度对齐;num_layers=1足够,堆到 2 层时dropout才生效,否则 PyTorch 会警告;dropout=0.5放在全连接前,防止 LSTM 输出被线性层死记。padding_idx=0让补零位置不产生梯度,避免噪声。如果显存不够,把embed_dim降到 64,hidden_dim降到 32,F1 掉不到 1 个点。
3.3 训练循环与早停策略
训练时用BCELoss配合Adam,学习率 1e-3,每轮记录验证集 F1,连续 3 轮不升就停。下面给出核心循环。
from sklearn.metrics import f1_score import numpy as np device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = BiLSTMClassifier(vocab_size=len(vocab)+1).to(device) criterion = nn.BCELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) def evaluate(model, X, y): model.eval() with torch.no_grad(): X_tensor = torch.LongTensor(X).to(device) preds = model(X_tensor).cpu().numpy() preds_binary = (preds > 0.5).astype(int) return f1_score(y, preds_binary) best_f1 = 0 patience = 3 wait = 0 for epoch in range(20): model.train() # 假设 DataLoader 已定义,batch_size=64 for batch_x, batch_y in train_loader: batch_x, batch_y = batch_x.to(device), batch_y.float().to(device) optimizer.zero_grad() outputs = model(batch_x) loss = criterion(outputs, batch_y) loss.backward() optimizer.step() val_f1 = evaluate(model, X_val, y_val) print(f'Epoch {epoch}, Val F1: {val_f1:.4f}') if val_f1 > best_f1: best_f1 = val_f1 torch.save(model.state_dict(), 'best_model.pt') wait = 0 else: wait += 1 if wait >= patience: print('Early stopping') breakbatch_size=64是平衡速度和梯度的常用值,显存小就降到 32。BCELoss要求标签是 float,所以batch_y.float()。早停的patience=3能防止过拟合,保存验证集 F1 最高的模型,而不是最后一轮。测试集只在最后跑一次,避免调参泄漏。
4. 避坑与排查:电商情感分析里最容易翻车的 5 个点
4.1 现象:验证集准确率 0.9,测试集掉到 0.6
原因:训练集和测试集分布不一致。常见于用公开数据训练、自建数据测试,公开数据偏书面语,自建数据全是「yyds」「绝绝子」这类网络梗,词表里没有,全映射成 0。解决:把自建数据里高频的新词加入 jieba 自定义词典,并在词表构建时降低频次阈值到 2,或者用预训练词向量(如腾讯词向量)初始化嵌入层。
4.2 现象:模型只预测正面,F1 为 0
原因:正负样本极度不平衡,且没加class_weight。解决:在BCELoss里传pos_weight参数,值设为负样本数/正样本数;或者对负面样本过采样。我一般先看混淆矩阵,如果负面召回率低于 0.3,就立刻处理不平衡。
4.3 现象:训练 loss 震荡不收敛
原因:学习率太大,或者序列长度不统一导致梯度爆炸。解决:把学习率降到 1e-4,加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5)。另外检查pad_sequences是否用了post,如果用pre补零,LSTM 读到末尾全是零会干扰隐状态。
4.4 现象:GPU 显存溢出
原因:MAX_LEN设太大,或者batch_size太高。解决:先打印一条评论的平均长度,取 95 分位数作为MAX_LEN;batch_size从 64 降到 16 试。如果还不行,把embed_dim和hidden_dim各减半。
4.5 现象:推理时单条预测结果和批量预测不一致
原因:model.eval()没加,Dropout 还在随机丢弃神经元。解决:推理前必须调model.eval(),并用torch.no_grad()包住。另外检查输入是否做了和训练时一样的分词、截断、填充,少一步都会导致分布偏移。
5. 把模型用起来:从单条预测到批量打分的工程技巧
训练完保存的best_model.pt只是权重,要落地得封装一个推理函数。下面这段代码加载模型,对任意中文评论输出情感概率,并给出阈值可调的判定。
def predict_sentiment(text, model, vocab, max_len=128, threshold=0.5): model.eval() words = clean_text(text) # 复用训练时的清洗函数 seq = [vocab.get(w, 0) for w in words] if len(seq) < max_len: seq = seq + [0] * (max_len - len(seq)) else: seq = seq[:max_len] tensor = torch.LongTensor([seq]).to(device) with torch.no_grad(): prob = model(tensor).item() return {'正面': prob, '负面': 1 - prob, '判定': '正面' if prob > threshold else '负面'} # 批量打分:读取 CSV,输出带情感标签的新文件 df_test = pd.read_csv('new_reviews.csv') df_test['sentiment'] = df_test['review'].apply(lambda x: predict_sentiment(x, model, vocab)['判定']) df_test.to_csv('scored_reviews.csv', index=False)threshold默认 0.5,如果业务更怕漏掉负面(比如退货预警),可以降到 0.3,牺牲一点精确率换召回率。批量打分时建议用DataLoader分批推理,比逐条快 10 倍以上。另外,把vocab和clean_text一起保存成 pickle,部署时直接加载,避免词表对不上。
进阶技巧:用torch.jit.trace把模型转成 TorchScript,推理速度提升 20%~30%,而且不依赖 Python 环境。对于毕业设计,这步能写进「工程化」章节,答辩时是加分项。我自己的习惯是每做完一个模型,先拿 20 条真实评论手动过一遍,看错例里有没有「反讽」或「双重否定」,如果有,就补进训练集再微调一轮。这个笨办法比盲目调参管用得多。希望帮到你。
本文还有配套的精品资源,点击获取