
简介本资源是一套基于LSTM的电影评论感情倾向分析Python实现方案面向计算机相关专业正在准备课程设计、期末大作业或毕业设计的学生以及需要项目实战练习的学习者。项目以IMDB影评数据为对象完成从文本预处理、词向量训练到LSTM情感二分类建模的完整流程并附带实践与作业手册可直接作为高分大作业参考。压缩包共22个文件约30.85MB包含3个Python源码文件、2个Markdown说明文档、1份PDF手册、1个训练好的模型文件、词向量模型及词表配置另有json配置、png结果图和arrow格式数据集等覆盖代码、数据、模型与报告全链路。目前已有156人学习下载。读者可获得可运行的训练与推理脚本、模型权重、实验图表和报告文档便于快速复现结果、理解LSTM在情感分析中的落地方式并在此基础上进行参数调整与功能扩展。1. 电影评论情感分析一份能跑通的 LSTM 毕设源码到底长什么样做课程设计最怕的不是不会写代码而是拿到一份跑不起来的源码。这份基于 LSTM 的电影评论感情倾向分析项目解压后目录结构清晰main.py是入口SimpleNN.py定义了网络结构train.py负责训练流程model/下存放train_config.json、word2vec.model、word2index.json等训练产物imdb_dataset/里是 train/test 数据report.md和实践与作业-手册.pdf则是配套报告与说明文档。它解决的核心问题很明确给定一段英文影评判断情感是正面还是负面。适合正在做 Python 期末大作业、需要完整训练推理链路的学习者也适合想搞清楚 LSTM 文本分类工程落地细节的熟手。下面我按实际拆包顺序把这份资源从环境配置到训练调参再到推理验证的完整路径讲透。2. 环境搭建与数据管线从解压到第一批 batch 进模型2.1 依赖安装与目录结构确认拿到压缩包后先别急着python main.py第一步是确认 Python 版本和依赖。这份源码用的是经典技术栈PyTorch 做 LSTM 实现gensim 训练 word2vec 词向量numpy/pandas 做数据处理。我一般会先建虚拟环境再装依赖避免和系统 Python 冲突。# 创建虚拟环境Python 3.8 兼容性最好 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装核心依赖 pip install torch numpy gensim pandas scikit-learn装完后检查目录重点确认三个文件是否存在model/word2index.json词到索引的映射、model/word2vec.model预训练词向量、model/train_config.json训练超参数。如果这三个文件缺失说明压缩包不完整需要重新获取。imdb_dataset/下应该有train和test两个子目录每个目录里是正负样本的文本文件。提示如果 pip 安装 torch 速度慢可以换用国内镜像源但不要混用多个源否则容易出现版本冲突。2.2 数据加载与词向量映射逻辑main.py里的数据加载部分是整个管线的入口。它做的事情是读取imdb_dataset下的原始文本用word2index.json把每个词转成整数索引再通过word2vec.model加载预训练向量初始化 embedding 层。这里有个关键参数max_len控制每条评论截断或填充后的统一长度。# main.py 中数据加载的核心逻辑简化示意 import json import torch from torch.utils.data import DataLoader, TensorDataset # 加载词表映射 with open(model/word2index.json, r) as f: word2index json.load(f) def text_to_indices(text, word2index, max_len200): 将评论文本转为索引序列超长截断不足补0 indices [word2index.get(w, word2index.get(UNK, 0)) for w in text.split()] if len(indices) max_len: return indices[:max_len] return indices [0] * (max_len - len(indices)) # 构建 Dataset 和 DataLoader # batch_size 在 train_config.json 中配置常见值为 32 或 64这段代码的逻辑说明word2index.get(w, word2index.get(UNK, 0))这行是处理未登录词的关键——如果某个词不在词表里就映射到UNK对应的索引如果连UNK都没有就落到 0。max_len200是经验值IMDB 影评平均长度在 200 词左右设太大显存吃不消设太小会丢失信息。batch_size从train_config.json读取我见过不少同学直接硬编码在代码里结果换数据集就翻车这份源码把它抽到配置文件里是加分项。2.3 训练配置文件的参数含义train_config.json是调参的主战场里面通常包含这些字段参数名典型值作用embedding_dim100/128词向量维度需与 word2vec.model 一致hidden_dim128/256LSTM 隐藏层维度越大拟合能力越强但易过拟合num_layers1/2LSTM 层数影评任务 1 层通常够用dropout0.3/0.5防过拟合训练时随机丢弃神经元batch_size32/64每批样本数受显存限制learning_rate1e-3/1e-4学习率Adam 优化器下 1e-3 是安全起点epochs10/20训练轮数配合早停策略使用改这些参数时有个血泪经验embedding_dim必须和word2vec.model训练时的维度一致否则加载预训练向量时会报维度不匹配。我一般会先用一个小脚本打印word2vec.model.vector_size确认。from gensim.models import Word2Vec model Word2Vec.load(model/word2vec.model) print(f词向量维度: {model.vector_size}) print(f词表大小: {len(model.wv)})输出结果直接告诉你 embedding_dim 该填多少别凭感觉猜。3. LSTM 模型结构与训练流程SimpleNN.py 里到底写了什么3.1 LSTM 层的前向传播与维度变换SimpleNN.py定义了一个继承自nn.Module的类核心是一个nn.LSTM加一个全连接分类头。LSTM 处理序列数据的逻辑是每个时间步输入一个词向量更新细胞状态和隐藏状态最后一个时间步的隐藏状态作为整条评论的表示送进全连接层做二分类。import torch.nn as nn class SimpleNN(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_layers, dropout): super(SimpleNN, self).__init__() self.embedding nn.Embedding(vocab_size, embedding_dim, padding_idx0) self.lstm nn.LSTM(embedding_dim, hidden_dim, num_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0) self.fc nn.Linear(hidden_dim, 2) # 二分类正面/负面 self.dropout nn.Dropout(dropout) def forward(self, x): # x shape: (batch_size, seq_len) embedded self.embedding(x) # (batch, seq_len, embedding_dim) lstm_out, (hidden, cell) self.lstm(embedded) # 取最后一个时间步的隐藏状态 last_hidden hidden[-1] # (batch, hidden_dim) out self.dropout(last_hidden) return self.fc(out) # (batch, 2)逻辑说明batch_firstTrue让输入维度是(batch, seq_len, embedding_dim)符合直觉。padding_idx0告诉 embedding 层索引 0 是填充位不参与梯度更新。hidden[-1]取的是最后一层 LSTM 在最后一个时间步的隐藏状态——注意不是lstm_out[:, -1, :]两者在单向 LSTM 下等价但多层时hidden[-1]更明确。dropout只在num_layers 1时传给 LSTM因为 PyTorch 对单层 LSTM 加 dropout 会警告。3.2 训练循环与损失函数选择train.py里的训练循环是标准写法前向传播、计算损失、反向传播、优化器更新。损失函数用CrossEntropyLoss优化器用Adam。import torch import torch.nn as nn from torch.optim import Adam # 初始化模型和优化器 model SimpleNN(vocab_sizelen(word2index), embedding_dim128, hidden_dim256, num_layers1, dropout0.3) criterion nn.CrossEntropyLoss() optimizer Adam(model.parameters(), lr1e-3) # 训练循环 for epoch in range(epochs): model.train() total_loss 0 for batch_x, batch_y in train_loader: optimizer.zero_grad() logits model(batch_x) loss criterion(logits, batch_y) loss.backward() # 梯度裁剪防止梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) optimizer.step() total_loss loss.item() print(fEpoch {epoch1}, Loss: {total_loss/len(train_loader):.4f})参数说明clip_grad_norm_的max_norm5.0是 LSTM 训练的常见保护措施梯度超过这个阈值就被缩放避免梯度爆炸导致 loss 变 NaN。optimizer.zero_grad()必须在loss.backward()之前调用否则梯度会累加。我见过有同学把zero_grad写在backward后面结果训练 loss 不降反升排查半天才发现是梯度累积问题。3.3 验证集评估与模型保存训练过程中需要定期在验证集上评估避免过拟合。评估时用model.eval()切换 dropout 和 batch norm 的行为并用torch.no_grad()关闭梯度计算节省显存。def evaluate(model, data_loader): model.eval() correct, total 0, 0 with torch.no_grad(): for batch_x, batch_y in data_loader: logits model(batch_x) preds torch.argmax(logits, dim1) correct (preds batch_y).sum().item() total batch_y.size(0) return correct / total # 每个 epoch 后在验证集上评估 val_acc evaluate(model, val_loader) print(fValidation Accuracy: {val_acc:.4f}) # 保存最佳模型 if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), model/best_model.pt)torch.argmax(logits, dim1)取的是两个类别中概率较大的那个索引0 代表负面、1 代表正面具体映射要看标签编码方式。保存state_dict()而不是整个模型加载时先实例化模型再load_state_dict()这样代码结构更清晰。4. 推理与可视化把训练好的模型用起来4.1 单条评论预测的完整流程训练完成后用main.py里的推理接口对任意英文影评做情感判断。流程是文本预处理 → 转索引 → 送模型 → 取 argmax。def predict(text, model, word2index, max_len200): model.eval() indices text_to_indices(text, word2index, max_len) tensor torch.tensor([indices], dtypetorch.long) with torch.no_grad(): logits model(tensor) prob torch.softmax(logits, dim1) pred torch.argmax(prob, dim1).item() label 正面 if pred 1 else 负面 confidence prob[0][pred].item() return label, confidence # 测试 text This movie is absolutely fantastic, the acting is superb. print(predict(text, model, word2index))torch.softmax把 logits 转成概率分布prob[0][pred]取预测类别的置信度。如果置信度低于 0.6说明模型对这条评论不太确定可能是文本里有反讽或混合情感实际使用时可以加一个阈值判断。4.2 训练曲线与结果图解读img/目录下有img.png到img3.png以及res.png这些是训练过程的可视化结果。常见的是 loss 曲线和 accuracy 曲线。看 loss 曲线时关注两点训练 loss 是否稳定下降、验证 loss 是否在某个 epoch 后开始上升。如果验证 loss 上升而训练 loss 继续下降说明过拟合了需要增大 dropout 或减少 epochs。res.png通常是混淆矩阵或分类报告的可视化。混淆矩阵能看出模型在正面和负面样本上的误判分布——如果负面样本被大量误判为正面可能是训练集里负面样本太少需要检查数据平衡性。注意如果 img 目录下的图片打不开或显示异常先确认是不是用相对路径引用的。有些源码在 report.md 里用引用但实际目录层级不对需要手动调整路径。5. 避坑与常见问题排查那些让你卡半天的细节5.1 词向量维度不匹配导致加载失败现象运行main.py时报RuntimeError: size mismatch for embedding.weight提示维度不一致。原因train_config.json里的embedding_dim和word2vec.model训练时的vector_size不一致。常见于手动改过配置但忘了同步词向量文件。解决用前面提到的脚本打印word2vec.model.vector_size把embedding_dim改成相同值。如果词向量文件本身损坏需要重新训练或从备份恢复。5.2 显存不足导致训练中断现象训练到一半报CUDA out of memory或者程序直接被 kill。原因batch_size或max_len设得太大或者hidden_dim过高。LSTM 的显存占用和batch_size × seq_len × hidden_dim成正比。解决先把batch_size减半试试如果还不行就降低max_len比如从 200 降到 150最后才考虑减小hidden_dim。另外检查是不是在 CPU 上跑——CPU 训练大模型也会内存不足加--device cpu或--device cuda明确指定设备。5.3 损失不下降或变为 NaN现象训练几个 epoch 后 loss 一直是 0.693二分类的随机水平或者突然变成 NaN。原因学习率太大导致梯度爆炸或者数据里有空文本导致除零。也有可能是标签编码反了——正面标成 0、负面标成 1但模型输出层没对应调整。解决先把学习率降到 1e-4 试试加上梯度裁剪clip_grad_norm_。检查数据加载部分有没有过滤掉空字符串。确认标签映射打印几条样本的batch_y看看正负样本的标签值是否和预期一致。5.4 推理时预测结果全是同一类现象不管输入什么评论模型都输出“正面”或都输出“负面”。原因训练集类别极度不平衡或者模型根本没学到东西欠拟合。也可能是推理时model.eval()忘了调用dropout 还在随机丢弃神经元。解决先检查训练集正负样本比例如果偏差太大需要重采样或加类别权重。确认推理代码里有model.eval()。如果训练 loss 本身就没降下来回到 5.3 排查训练问题。5.5 report.md 里的路径引用错误现象用 Markdown 预览器打开report.md时图片显示不出来或者 PDF 手册里的代码和实际源码对不上。原因报告是在不同目录结构下写的图片相对路径和当前解压后的路径不一致。PDF 手册可能是早期版本和源码有细微差异。解决以源码为准报告作为参考。图片路径手动改成img/xxx.png或绝对路径。如果 PDF 和源码冲突优先信源码——毕竟代码能跑通才是硬道理。6. 进阶技巧用预训练词向量微调提升小数据集表现这份源码默认用 word2vec 静态词向量embedding 层在训练时也会更新。但在数据量不大时比如只有几千条评论冻结 embedding 层或者用更小的学习率微调往往比全量更新效果更好。我一般会这样做先把embedding.weight.requires_grad设为False只训练 LSTM 和全连接层跑几个 epoch 后再解冻做微调。# 冻结 embedding 层 model.embedding.weight.requires_grad False # 只优化 LSTM 和 FC 参数 optimizer Adam(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3) # 训练几个 epoch 后解冻 for epoch in range(5): train_one_epoch(model, train_loader, optimizer, criterion) model.embedding.weight.requires_grad True optimizer Adam(model.parameters(), lr1e-4) # 微调时用更小学习率 for epoch in range(5, 10): train_one_epoch(model, train_loader, optimizer, criterion)这个策略的好处是前期冻结 embedding 防止随机初始化的分类头把预训练词向量带偏后期小学习率微调让词向量适应具体任务。实测在 IMDB 子集上能提升 2~3 个百分点的验证准确率。另一个技巧是调整max_len的截断策略。默认是从头截断取前 200 个词但影评的关键情感词往往在结尾。改成从尾部截断或者取头尾各 100 词拼接有时效果更好。具体哪种策略更优得在验证集上试——没有放之四海而皆准的答案。验证模型是否真的学到了东西我习惯用几个“对抗样本”测试把明显正面的评论加个否定词“not good”看模型能不能翻转为负面。如果翻转失败说明模型可能只是记住了关键词而不是理解语义。这时候可以考虑加注意力机制但那就是另一个话题了。从那以后我每次拿到文本分类源码都强制先跑一遍小样本过拟合测试取 100 条数据看模型能不能在 50 个 epoch 内把训练准确率拉到 95% 以上。如果连过拟合都做不到说明模型结构或数据管线有问题调参也是白调。希望帮到你。本文还有配套的精品资源点击获取