资讯详情

Python基于LSTM三分类情感分析:从数据到部署完整实战

📅 2026/10/12 19:23:10 | 华诺云谱 👁 阅读
Python基于LSTM三分类情感分析:从数据到部署完整实战
简介这份资源是Python基于LSTM三分类的文本情感分析完整项目面向计算机相关专业正在做课程设计、期末大作业的学生以及需要项目实战练习的学习者。项目将文本情感划分为三类采用LSTM网络建模配套文档说明代码完整可运行适合作为高分课程设计参考。压缩包共13个文件约11.63MB包含3个csv情感语料、3个py训练与测试脚本、2个ipynb实验笔记、1个pkl词向量模型、1个h5训练权重、1个yml配置、1个txt依赖清单及md说明文档覆盖数据预处理、词向量加载、模型训练与评估全流程。目前已有307人学习下载。读者可据此掌握从语料读取、Word2vec特征构建到LSTM三分类建模的完整方案理解训练脚本与测试脚本的分工并借助配置文件和权重文件快速复现实验结果为后续文本分类任务提供可迁移的工程模板。1. 三分类情感分析到底在分什么从二分类翻车说起做过情感分析的人大多从二分类起步好评、差评一条阈值切下去就完事。真到业务里你会发现用户评论里大量是「还行吧」「凑合能用」「不算差但也不推荐」这类中间态硬塞进正负两极模型准确率看着挺高上线后运营却天天吐槽分错。三分类要解决的就是这个把情感切成负面、中性、正面三档让「中立」有地方待。这个 Python 基于 LSTM 三分类的文本情感分析项目核心就是用 LSTM 神经网络对中文文本做三分类配套源码和文档说明适合想跑通一条完整 NLP 流水线的人——从数据清洗、分词、词向量到模型训练和推理。它不追求 SOTA追求的是你能在自己机器上复现、能改、能看懂每一层在干什么。下面按「数据怎么备、模型怎么搭、坑在哪」一路讲透。2. 数据准备与标签体系三分类的样本从哪来2.1 三分类标签怎么定义才不自相矛盾二分类的标签是天然的三分类的「中性」是人为划出来的这一步没定义清楚后面模型再强也白搭。常见做法是给每条样本打一个情感极性分比如 -1、0、1或者用 1~5 星映射1~2 星归负面3 星归中性4~5 星归正面。我一般会先写死一套标注规则再动手避免边标边改。标签编码典型文本特征常见来源负面0差、垃圾、失望、退货1~2 星评论、投诉工单中性1还行、一般、凑合、无感3 星评论、客观描述正面2好、推荐、满意、回购4~5 星评论、好评注意中性类最难标也最容易和另外两类混淆。实操里我会把「没有明显情感词、以陈述事实为主」的句子优先归中性而不是靠感觉。如果数据源本身只有正负两档可以拿「无情感倾向的客观句」补充中性样本但别用机器硬造否则中性类会学成一锅粥。2.2 中文分词与停用词处理的最小流程中文不像英文有空格LSTM 吃的是词序列分词质量直接决定上限。项目里一般用 jieba 做分词配合一份停用词表过滤掉「的、了、是」这类高频无信息词。下面是一段可直接抄的预处理代码import jieba import re # 加载停用词表一行一个词 def load_stopwords(path): with open(path, r, encodingutf-8) as f: return set(line.strip() for line in f if line.strip()) stopwords load_stopwords(stopwords.txt) def clean_text(text): # 去掉 URL、、# 话题标签等噪声 text re.sub(rhttp\S|\w|#\S#, , text) # 只保留中文、英文、数字 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text) return text.strip() def tokenize(text): text clean_text(text) # 精确模式分词过滤停用词和单字 words [w for w in jieba.lcut(text) if w not in stopwords and len(w) 1] return words逻辑说明clean_text先把 URL、、话题标签这些在社交文本里高频但对情感判断无用的东西清掉再用正则只留中英文数字避免标点符号污染词表。tokenize用 jieba 精确模式切词过滤停用词和长度为 1 的字——单字在中文里歧义太大留着反而增加噪声。参数说明len(w) 1这个阈值不是死的如果你的领域里有「差」「赞」这种单字强情感词可以放宽到 1但要观察词表膨胀情况。停用词表建议用哈工大或百度那份通用表打底再按自己数据补领域词。2.3 构建词表与序列填充padding 长度怎么定LSTM 要求输入是定长序列所以得先统计句子长度分布再定max_len。我一般会画一下长度分布取覆盖 95% 样本的那个长度而不是拍脑袋定 100 或 200。from collections import Counter import numpy as np # 统计所有句子分词后的长度 lengths [len(tokenize(t)) for t in raw_texts] print(长度分布:, np.percentile(lengths, [50, 90, 95, 99])) # 取 95 分位作为 max_len max_len int(np.percentile(lengths, 95)) # 构建词表按词频排序保留 top 10000 word_counter Counter(w for t in raw_texts for w in tokenize(t)) vocab {w: i 2 for i, (w, _) in enumerate(word_counter.most_common(10000))} vocab[PAD] 0 # 填充符 vocab[UNK] 1 # 未登录词 def encode(text): words tokenize(text) ids [vocab.get(w, vocab[UNK]) for w in words] # 截断或填充到 max_len if len(ids) max_len: ids [vocab[PAD]] * (max_len - len(ids)) else: ids ids[:max_len] return ids逻辑说明先统计长度分位数用 95 分位定max_len既覆盖绝大多数句子又不至于让 padding 占太多。词表用PAD占 0、UNK占 1真实词从 2 开始编号这是 PyTorch 里Embedding层的常见约定。encode负责把词转成 id 并统一长度。参数说明most_common(10000)里的 10000 是词表上限中文情感任务一般 1 万到 3 万够用太小会大量UNK太大则 embedding 参数膨胀。max_len如果取 95 分位后还是很大比如超过 300要考虑是不是有超长噪声文本先清洗再统计。3. LSTM 三分类模型搭建从 Embedding 到 Softmax3.1 为什么选 LSTM 而不是 TextCNN 或 BERT先说选型理由免得你盲目跟。TextCNN 快、参数少但它的卷积核感受野固定对「虽然…但是…」这种长距离转折不敏感BERT 效果好但显存和推理成本高小数据集上还容易过拟合。LSTM 的门控机制天然适合处理变长序列和长距离依赖参数量适中在几千到几万条的中文情感数据上是性价比很高的选择。这个项目用 LSTM 三分类定位就是「单卡能跑、代码能看懂、效果够用」。常见做法是单向 LSTM 取最后一个时间步的隐状态接全连接层输出 3 类。如果数据量够可以换双向 LSTMBiLSTM把正向和反向的最后隐状态拼接效果通常有提升代价是训练慢一倍。3.2 PyTorch 版 LSTM 分类网络完整代码import torch import torch.nn as nn class LSTMClassifier(nn.Module): def __init__(self, vocab_size, embed_dim128, hidden_dim128, num_layers1, num_classes3, dropout0.3): super().__init__() # 词嵌入层padding_idx0 保证 PAD 不参与梯度 self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.lstm nn.LSTM( input_sizeembed_dim, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue, # 输入形状 (batch, seq, feature) bidirectionalFalse, dropoutdropout if num_layers 1 else 0 ) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_dim, num_classes) def forward(self, x): # x: (batch, seq_len) emb self.embedding(x) # (batch, seq, embed_dim) out, (h_n, c_n) self.lstm(emb) # out: (batch, seq, hidden) # 取最后一个时间步的隐状态 last out[:, -1, :] # (batch, hidden) last self.dropout(last) logits self.fc(last) # (batch, num_classes) return logits逻辑说明Embedding把词 id 映射成稠密向量padding_idx0让填充位不产生梯度避免 PAD 影响训练。LSTM 设batch_firstTrue后输入输出都是(batch, seq, feature)取out[:, -1, :]就是最后一个时间步的输出代表整句的语义摘要。最后接 Dropout 和全连接层输出 3 个 logits交给 CrossEntropyLoss。参数说明embed_dim128和hidden_dim128是中小数据集的常用起点数据量大可以加到 256。num_layers1时 dropout 不生效PyTorch 会警告要堆两层以上才需要。dropout0.3是防过拟合的常规值如果训练集很小可以提到 0.5。3.3 训练循环与关键超参设置from torch.utils.data import DataLoader, TensorDataset from torch.optim import Adam import torch.nn as nn # 假设 X_train, y_train 已经 encode 好 X_train torch.tensor([encode(t) for t in train_texts], dtypetorch.long) y_train torch.tensor(train_labels, dtypetorch.long) dataset TensorDataset(X_train, y_train) loader DataLoader(dataset, batch_size64, shuffleTrue) device torch.device(cuda if torch.cuda.is_available() else cpu) model LSTMClassifier(vocab_sizelen(vocab)).to(device) criterion nn.CrossEntropyLoss() optimizer Adam(model.parameters(), lr1e-3) for epoch in range(10): model.train() total_loss 0 for xb, yb in loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() logits model(xb) loss criterion(logits, yb) loss.backward() # 梯度裁剪防止 LSTM 梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) optimizer.step() total_loss loss.item() print(fepoch {epoch1}, loss {total_loss/len(loader):.4f})逻辑说明TensorDataset把编码后的输入和标签打包DataLoader负责分批和打乱。训练循环里标准五步清零梯度、前向、算损失、反向、更新。clip_grad_norm_是 LSTM 训练的必备操作梯度爆炸是 RNN 系模型的经典翻车点。参数说明batch_size64是显存和收敛稳定性的折中显存小就降到 32。lr1e-3是 Adam 的常用学习率如果 loss 震荡明显可以降到 5e-4。max_norm5.0是梯度裁剪阈值太小会拖慢收敛太大等于没裁5 是经验值。训练轮数别死守 10要看验证集 loss早停比硬跑更靠谱。4. 避坑与排查三分类项目最容易翻车的五个地方4.1 类别不平衡导致模型只预测多数类现象训练准确率很高但混淆矩阵里中性类几乎全被预测成正面或负面中性类的召回率低得离谱。原因三分类数据里中性样本往往最少CrossEntropyLoss 默认每类权重相同模型为了降低总损失会偏向多数类。解决给损失函数加类别权重权重和类别频率成反比。criterion nn.CrossEntropyLoss(weighttorch.tensor([1.0, 2.5, 1.0]).to(device))中性类权重调高。或者用重采样让三类样本数量接近。改完记得重新看混淆矩阵别只看准确率。4.2 分词把情感词切碎了现象模型对「不推荐」和「推荐」分不出来负面样本被大量判成正面。原因jieba 默认会把「不推荐」切成「不」和「推荐」而「不」如果进了停用词表就被过滤掉情感直接反转。解决把否定词从停用词表里拿掉或者用 jieba 的add_word把「不推荐」「不好用」这类组合词加进词典。更稳的做法是保留否定词让 LSTM 自己学「不」和后面词的依赖关系。4.3 padding 位置参与了注意力或池化现象换成 BiLSTM 加平均池化后短句效果明显变差。原因平均池化把 PAD 位置的输出也算进去了短句 padding 多被稀释得厉害。解决池化前用 mask 把 PAD 位置屏蔽掉或者干脆用最后时间步而不是平均池化。如果一定要池化按序列真实长度做 masked mean。4.4 验证集和测试集分布不一致现象验证集准确率 0.9上线后掉到 0.6。原因划分数据时没打乱或者验证集来自不同时间段、不同来源分布和训练集差太多。解决划分前先 shuffle再按 8:1:1 切。如果数据有时间属性按时间切比随机切更接近真实场景。切完对比一下三类的比例差异大就重新切。4.5 推理时忘了切 eval 模式现象同一句话多次预测结果不一样或者推理结果比验证时差。原因模型还在 train 模式Dropout 还在随机丢弃神经元。解决推理前调model.eval()并用with torch.no_grad():包住前向既关掉 Dropout 又省显存。这是新手最常踩的坑代码里加一行就能避免。5. 推理部署与效果验证把模型用起来5.1 单条文本推理的封装训练完的模型要能对外提供服务封装一个预测函数是最小落地单元。def predict(text, model, vocab, max_len, device): model.eval() ids encode(text) # 复用训练时的 encode x torch.tensor([ids], dtypetorch.long).to(device) with torch.no_grad(): logits model(x) prob torch.softmax(logits, dim1) pred torch.argmax(prob, dim1).item() label_map {0: 负面, 1: 中性, 2: 正面} return label_map[pred], prob[0][pred].item()逻辑说明model.eval()关掉 Dropouttorch.no_grad()关闭梯度计算。softmax把 logits 转成概率argmax取最大概率的类别。返回标签和置信度置信度低于阈值时可以转人工复核。参数说明encode必须和训练时完全一致包括分词、词表映射、max_len任何一处不一致都会导致推理结果漂移。置信度阈值一般设 0.6~0.7低于这个值说明模型没把握。5.2 用混淆矩阵和分类报告验证三分类效果准确率在三分类里会骗人必须看每一类的表现。from sklearn.metrics import classification_report, confusion_matrix model.eval() all_preds, all_labels [], [] with torch.no_grad(): for xb, yb in test_loader: xb xb.to(device) preds torch.argmax(model(xb), dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(yb.numpy()) print(confusion_matrix(all_labels, all_preds)) print(classification_report(all_labels, all_preds, target_names[负面, 中性, 正面]))逻辑说明遍历测试集收集所有预测和真实标签用 sklearn 输出混淆矩阵和分类报告。混淆矩阵看错分方向分类报告看每类的 precision、recall、f1。参数说明重点盯中性类的 f1它通常最低。如果中性类 f1 低于 0.5回去查标签定义和类别权重。三分类的 macro f1 比 accuracy 更能反映真实水平。5.3 一个我常用来压测的小技巧模型训完别急着高兴拿一批「对抗样本」试试把正面句子里的情感词换成同义但更弱的词看模型会不会掉到中性把中性句子加个「太」字看会不会跳到正面。这种边界测试能快速暴露模型是真学到了语义还是只记住了关键词。我一般会手工构造 20 条边界样本每次改完模型都跑一遍比看验证集 loss 直观得多。说到底三分类情感分析这个方向值不值得做取决于你的业务里「中性」是不是真的需要单独处理。如果中间态占比超过两成硬做二分类就是自欺欺人那这套 LSTM 三分类的源码和流程就值得你花一个周末跑通。我自己的习惯是先把数据标签定义写死在文档里再动模型代码否则后面调参调到怀疑人生都不知道问题出在哪。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑