
简介基于Python与LSTM的文本情感分析系统源码适合自然语言处理课程学习者、期末大作业或毕业设计需要完整可运行项目的学生源码覆盖数据读取、分词、词表构建、LSTM模型训练与情感预测并包含机器人对话式交互模块。压缩包共9个文件大小约7.15MB主要包含3个可执行Python脚本训练、预测和对话、1个训练好的模型权重文件、1个词表文件、1份JSON格式训练集另有说明文档、网络结构图和系统效果图整体目录结构清晰便于按模块定位代码与资料。训练好的权重与词表可直接加载省去重新训练时间既能对照学习完整实现流程也方便二次开发或答辩展示。该项目已在本地编译运行通过评审分达98分内容经助教老师审定难度适中适合快速用于课程设计或毕业设计参考。目前已有140人学习下载是掌握LSTM情感分析工程落地的高分参考资料。1. PythonLSTM文本情感分析系统源码跑通它需要知道的三件事如果你正在找期末大作业、毕业设计或者课程设计要用的 Python 源码这套基于 Python 与 LSTM 的文本情感分析系统值得先看一眼。它不是只有一个孤零零的 train.py而是把训练、预测、对话演示、模型文件和数据全都配齐了解压后按 README 走一遍就能跑通评审分 98 分难度介于入门和提高之间。适合期末大作业、课程设计报告也适合刚学完 LSTM 想快速做一个小项目验证自己能力的同学。下面我把工程结构、LSTM 原理、训练与预测实操完整拆一遍包括我复现过程中踩过的坑。2. 工程结构逐层拆解train.py、predict.py 与模型文件怎么协作拿到一个课程设计项目我的习惯是先花十几分钟看目录结构再决定从哪个文件入手。这套源码的文件围绕一条明确的主线读入训练数据训练 LSTM 情感分类模型保存模型和词表最后加载模型对输入文本做情感判断。把这条主线理清楚后面跑代码和排错都会顺手很多期末答辩时老师问起模块划分你也能讲明白。2.1 目录与文件清单五个模块各自的职责解压后的顶层目录结构大致是这样bash main/ ├── data/ │ └── train.json ├── img/ │ ├── LSTM-Nework.png │ └── system_describe.png ├── model/ │ ├── my_model.h5 │ └── word_dict.pickle ├── predict.py ├── robot_chat.py ├── train.py └── README.mddata 目录放训练数据model 目录放训练产物根目录下三个 Python 文件分别负责训练、预测和对话演示。img 目录里的两张图是课程设计报告里要引用的 LSTM 网络架构图和系统描述图写报告的时候直接复用比自己用 Visio 重新画省事得多也保证了图表风格和代码实现一致。README.md 建议第一时间打开一般会写明依赖版本、运行顺序和演示效果。很多同学拿到源码第一件事就是运行 train.py报错之后开始怀疑资源有问题其实是环境没对上。我的顺序是先看 README确认 Python 版本和依赖再跑一个简单的 import 检查最后才动手训练。这套流程听起来慢实际上能帮你少走半小时弯路。三个 Python 文件的分工很明确。train.py 读数据、构建模型、训练并保存 my_model.h5 和 word_dict.picklepredict.py 加载模型和词表做单条预测robot_chat.py 在预测基础上包装成对话机器人。跑通这三个文件整个系统的闭环就完整了。注意这只是一个课程设计级别的工程目录结构不会像工业项目那么复杂但这个分工方式对报告里的模块设计章节已经足够也是答辩时最容易讲清楚的一个点。2.2 train.json看懂训练数据的字段和标签分布训练数据放在 data/train.json一个 JSON 数组每个元素是一条样本。常见格式是 text 和 label 两个字段text 存原始文本label 存情感类别比如json [ {text: 这部电影真的很好看剧情紧凑, label: 正面}, {text: 服务态度差等了一个小时都没上菜, label: 负面}, {text: 一般般吧没有想象中那么惊艳, label: 中性} ]具体字段名可能因为课程设计的要求略有差异但结构基本是 text-label 对。train.py 读入之后会把 text 列表和 label 列表分开label 再做一次字符串到数字的映射比如“正面”映射到 0、“负面”映射到 1、“中性”映射到 2。你也可以自己定义映射规则只要训练和预测两边的映射关系保持一致即可。动手训练之前我建议先跑一个快速检查脚本确认三件事样本总量、标签分布、文本长度范围。这三个数字直接影响训练参数设置python import jsonwith open(data/train.json, r, encodingutf-8) as f: data json.load(f)print(总条数:, len(data))labels {} lengths [] for item in data: labels[item[label]] labels.get(item[label], 0) 1 lengths.append(len(item[text]))print(标签分布:, labels) print(平均文本长度:, sum(lengths) / len(lengths)) print(最长文本:, max(lengths), 最短文本:, min(lengths))这段脚本先读取训练集然后依次统计类别样本数和文本长度的均值与极值。标签分布能看出类别是否平衡如果“正面”样本是“负面”的四倍模型大概率会偏向输出频率高的类别文本长度的均值帮助你决定 max_len 参数设太小长句会被截断丢关键语义设太大短句会被大量填充浪费算力还容易过拟合。我一般会在这个阶段顺手把 label 到 id 的映射表打印出来和 train.py 里的映射规则对照一遍。如果映射不一致就会出现训练时用 0 表示“正面”、预测时 0 却代表“负面”这种低级错误排查起来非常消耗时间。别问我是怎么知道的。2.3 模型与词表word_dict.pickle 和 my_model.h5 的前后关系训练完成之后model 目录下会出现两个关键文件词表 word_dict.pickle 和模型 my_model.h5。词表把中文词映射成整数索引模型保存了 LSTM 网络的权重。这两个文件强绑定词表变了同一个词映射出的索引就变了喂给模型的序列形状和语义编码就全乱了。所以下载资源里的这两个文件要配套使用不要单独替换其中一个。predict.py 加载它们的标准代码是这样的python import pickle from tensorflow.keras.models import load_modelwith open(model/word_dict.pickle, rb) as f: word_dict pickle.load(f)model load_model(model/my_model.h5)pickle.load 读出词表load_model 读出 Keras 模型。注意顺序先读词表再加载模型。因为你马上要对输入文本做序列化序列化依赖词表如果顺序反过来也不会报错但代码逻辑上会别扭后续处理文本时还得回头去补读词表没必要制造这种混乱。词表内部结构可能是 {word: index} 也可能是 {index: word}两种结构代码写法完全不同。常见做法是训练时保存 {word: index}预测时直接查 word_dict[电影] 得到索引。假如遇到未知词直接查字典会抛 KeyError所以 predict.py 里一般会预留一个处理分支比如越过未知词或者用 0 填充。知道词表的键值方向能帮你快速判断报错发生在序列化阶段还是模型推理阶段。另外如果你想重新训练一个自己的模型记得先备份或删掉旧的 my_model.h5 和 word_dict.pickle。否则新模型训练完保存时会覆盖旧文件中间如果训练中断加载到的还是旧模型你以为是自己的新模型在预测实际是旧模型在跑这种错位很难发现。这一点在后面的避坑章节会再展开。3. LSTM 情感分析原理为什么门控机制能记住“不差”和“太差”情感分析的本质是让模型从一段文本里判断情绪倾向。对中文句子来说语义不只靠单个词还依赖词与词之间的顺序和上下文。比如“电影不差”和“电影太差”只差一个字结论完全相反。传统的词袋模型把文本当成一堆词的集合丢掉了顺序信息所以在这个任务上表现受限。LSTM 属于循环神经网络家族天然为序列建模设计能一步步读入词语把前面出现过的信息保留在记忆单元里因此成为文本情感分类的常见选择。3.1 从 RNN 梯度消失到 LSTM 门控情感分析为什么吃序列模型最早处理序列的循环神经网络 RNN结构上每一步读入当前输入和上一步的隐藏状态形成一个时间维度上的链式结构。理论上它能记住很久以前的信息但实际训练时存在梯度消失问题误差在反向传播经过多个时间步之后梯度数值会指数级衰减导致远距离的依赖关系学不到。对情感分析来说转折词、否定词常常出现在句子的开头或中间和结论隔着好几个词纯 RNN 很难把这个关系学明白所以情感分类任务里 LSTM 明显更常见。LSTM 的改进思路是加入门控机制让信息可以选择性通过。它维护一条长期记忆通道叫作细胞状态通过遗忘门、输入门、输出门三个门来控制信息的保留、更新和输出。这样梯度可以沿着细胞状态这条通道走捷径远距离的信息衰减速度大大降低。可以这样理解LSTM 相当于在 RNN 的流水线上加了一条传送带关键信息放传送带上慢慢传不会被中间环节冲掉。对课程设计来说你不需要把每个数学公式背下来但要能在报告里说清楚三个门的作用遗忘门决定要不要丢掉旧信息输入门决定新信息写入多少输出门决定当前时刻输出什么。这三个门都是带 sigmoid 激活的神经网络层输出值在 0 到 1 之间0 表示完全挡住1 表示完全放行。门控设计正是 LSTM 在处理文本序列时表现优于普通 RNN 的核心原因答辩时把这个逻辑讲出来老师基本不会再追问。3.2 遗忘门、输入门、输出门LSTM 内部更新的数据流一个 LSTM 单元在一个时间步的数据流可以简单描述为输入当前词和上一个隐藏状态拼接之后分别送入三个门和一个候选记忆生成单元。遗忘门的输出决定细胞状态里哪些旧信息保留输入门的输出决定候选记忆的哪些部分被纳入细胞状态更新后的细胞状态是旧状态乘以遗忘门加上新候选乘以输入门。最后输出门和更新后的细胞状态共同决定本步的隐藏输出。下面是一个用 Keras 搭建的 LSTM 情感分类模型的最小示例展示了核心层结构python from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropoutmodel Sequential([ Embedding(input_dimvocab_size, output_dimembed_dim, input_lengthmax_len), LSTM(hidden_dim, dropout0.2, recurrent_dropout0.2), Dropout(0.3), Dense(num_classes, activationsoftmax) ])model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy])这里的 Embedding 层把每个词的整数索引转成稠密嵌入向量LSTM 层读入整个序列并输出最后时刻的隐藏状态Dropout 丢弃部分节点防止过拟合最后的 Dense 层加上 softmax 输出每个类别的概率。hidden_dim 是 LSTM 隐藏单元数一般取 64 或 128embed_dim 是词向量维度常用 100 到 300 之间。这两个参数越大模型学习能力越强但参数量也越多训练时间变长对课程设计的小数据集来说 64 到 128 完全够用。如果你用的是 Keras 的 to_categorical 做 one-hot 编码需要确认 num_classes 的取值和实际类别数一致。另一种做法是用 sparse_categorical_crossentropy 作为损失函数可以直接吃整数标签省掉 one-hot 步骤。两种写法的差异会延伸到 predict.py 里的解码逻辑如果训练时用 one-hot预测结果 argmax 后拿到的就是类别索引如果训练时用稀疏标签预测时对概率做 argmax 也一样。关键是别混用。3.3 嵌入层与文本序列化从中文句子到张量模型不能直接吃中文文本训练前需要把句子转成等长的整数序列。这个过程的常规链路是中文分词 → 词到索引映射 → 序列对齐与填充。分词可以用 jieba也可以用简单的按字切分词到索引映射查 word_dict序列对齐用 pad_sequences 统一到 max_len。下面是一段典型的序列化代码python import jieba from tensorflow.keras.preprocessing.sequence import pad_sequencesdef text_to_sequence(text, word_dict, max_len32): words list(jieba.cut(text)) seq [word_dict.get(w, 0) for w in words] seq seq[:max_len] return pad_sequences([seq], maxlenmax_len, paddingpost)sequence text_to_sequence(这家店的东西挺好吃的, word_dict) prob model.predict(sequence)先分词再逐个查词表得到索引序列截断到 max_len最后 pad_sequences 把长度不足的部分用 0 补在后面。注意 word_dict.get(w, 0) 里默认值 0 要确保不在字典的合法索引范围内否则会把未知词当成合法词处理。paddingpost 表示在序列尾部填充和训练时的填充方式保持一致。model.predict 返回的是一组概率值比如 [0.12, 0.75, 0.13]取 argmax 就是预测类别。这里的 max_len、vocab_size、embed_dim、hidden_dim 在 train.py 和 predict.py 里必须保持一致。如果训练时 max_len 是 64预测时写成 80输入序列多出的部分用 0 填充整个样本的语义分布就和训练阶段完全不同预测结果自然会有偏差。把这些超参数放在一个 config 字典或者单独的配置文件里统一管理读取时只改一处是最省心的做法也是工程上比“散落在代码里的魔法数字”更规范的习惯。4. 从训练到预测全流程实操参数怎么改、效果怎么看前面把原理和结构讲清楚了这一章进入实际操作。我会按环境准备、训练、预测三个环节一步步写每一步都会给出可复制的命令和代码同时标注关键参数的影响方便你根据自己的数据集调整。4.1 环境准备Python 版本与依赖安装运行这套源码需要 Python 3.6 以上版本我一般用 3.8稳定性最好。核心依赖是 TensorFlow 2.x内置 Keras API、numpy、jieba以及 Python 自带的 json、pickle 模块。安装命令很简单bash pip install tensorflow2.10.0 numpy jieba如果你用的是 TensorFlow 2.10 以上版本Keras 接口已经合并到 tf.kerastrain.py 里要么写 from tensorflow.keras 开头的导入要么写传统 keras这两个在旧代码里是混用的重灾区。建议打开 train.py 和 predict.py 先看一眼 import 语句确认是 from tensorflow.keras 还是 from keras。如果文件里写的是 keras就安装对应版本如果写的是 tensorflow.keras就装 TensorFlow 2.x。课程设计项目常用后者因为 tf.keras 不需要单独安装 keras 包。依赖装好后用下面这段脚本快速验证环境python import tensorflow as tf import jieba print(TensorFlow版本:, tf.version) print(jieba版本:, jieba.version) print(GPU可用:, tf.config.list_physical_devices(GPU) is not None)这段代码打印 TensorFlow 和 jieba 版本并检查是否有 GPU。没有 GPU 也能跑只是慢一点。对这个小规模情感分析数据集CPU 训练一个 epoch 大概几秒到十几秒完全够用。如果这里 import 就报错问题基本出在依赖安装环节先把环境理顺再进入下一步不要在报错状态下继续往下跑。4.2 运行 train.py训练循环与模型保存环境确认好了就开始训练。在 main 目录下执行bash python train.py一个正常的训练过程会在终端里逐轮打印 loss 和 accuracy 变化类似这样bash Epoch 1/20 0.6891 - accuracy: 0.5534 Epoch 2/20 0.6218 - accuracy: 0.6142训练完成之后train.py 会执行 model.save(model/my_model.h5)同时把词表 dump 成 word_dict.pickle。这两个文件就是 2.3 节里 predict.py 依赖的东西。如果训练中途中断这两个文件可能只生成一个或者根本没写入下次预测时会直接报文件不存在。训练过程中几个关键参数决定了效果epochs遍历训练集的轮数20 轮对课程设计的小数据集来说可能已经过拟合表现是验证集 loss 先降后升。看日志判断accuracy 涨到平台就停。batch_size每次送入模型的样本数常见取值 32 或 64。越大训练越快但收敛可能不稳越小更稳定但更慢32 起步即可。learning_rateAdam 优化器默认是 0.001。如果 loss 震荡剧烈调成 0.0001 试一下如果模型收敛太慢再调回去。这个参数的影响比 batch_size 更敏感。注意如果你训练的 epochs 比较大建议在 train.py 里加一个 EarlyStopping 回调监控验证集 losspatience 设 3。这是 Keras 标准做法加几行代码能省不少调参时间报告里也能体现你的工程意识。4.3 predict.py 实操单条预测、批量预测和类别解码train.py 跑通之后重点转到 predict.py。单条预测的流程前面已经拆过predict.py 把 text_to_sequence、模型推断、概率解码封装成了一个完整函数。打开文件找到核心函数一般长这样python def predict_sentiment(text): seq text_to_sequence(text, word_dict, max_len32) prob model.predict(seq)[0] label_id int(prob.argmax()) label id_to_label[label_id] confidence float(prob[label_id]) return label, confidence参数说明text 是输入的一句话word_dict 是训练阶段保存的词表max_len 要和训练时一致。id_to_label 是训练时 label 映射的反向字典比如 {0: 负面, 1: 正面, 2: 中性}。返回的是预测类别和置信度。批量预测可以直接循环调用这个函数或者把多条文本组织成一个列表后一次性 predictpython texts [这个电影太棒了, 昨天退货遇到客服不理人, 凑合看吧] for t in texts: label, conf predict_sentiment(t) print(f{t} - {label} ({conf:.2f}))这里如果想一次传多句需要先分别 text_to_sequence 再拼接成一个批次因为 model.predict 期望输入是二维数组。对课程设计来说循环调用完全够用也能在每句话后面打出置信度方便写进报告里的效果展示表格。置信度低于 0.4 的预测结果基本不可信建议在代码里加一个判断低于阈值就提示“无法判断”这样更接近真实产品的交互逻辑。5. 常见报错与避坑记录四个高频问题都出在哪这是这套资源里最容易翻车的地方我自己在复现过程中遇到过的几个问题都整理在下面按现象、原因、解决三个角度写。遇到同类型问题可以直接按这个路径排查。5.1 现象load_model 报 TypeError 或文件格式错误运行 predict.py 时load_model(model/my_model.h5) 抛出 TypeError:init() got an unexpected keyword argument name或者直接提示 file format not supported。原因是 my_model.h5 是用旧版本 TensorFlow1.x 或 2.0 早期保存的 Keras 模型而当前环境装的是 TensorFlow 2.10 以上。Keras 内部反序列化的接口变了旧权重文件和新环境不兼容。另一个常见变体是 h5 文件用独立 keras 包保存tf.keras 加载时也会遇到类似的坑。解决最省事的办法是锁定旧版本环境比如 pip install tensorflow2.10.0 numpy1.24在旧环境下重新加载。另一种办法是转换模型格式但步骤繁琐课程设计直接锁版本更稳妥。如果你打算重新训练直接在当前环境跑 train.py 生成新的 h5 文件predict.py 再加载新文件也是一条干净的路。5.2 现象读取 train.json 抛 UnicodeDecodeError在 Windows 上运行 train.py打开 train.json 时报错 UnicodeDecodeError: gbk codec cant decode byte。原因是 Windows 系统默认编码是 GBK而 train.json 保存为 UTF-8 格式。open() 没有手动指定 encodingPython 按系统默认编码去读 UTF-8 文件遇到底层字节序列解析不出就抛异常。解决所有打开文件的地方统一写成 open(data/train.json, r, encodingutf-8)。不只是数据文件词表 pickle 和 README 也一样。这是中文 NLP 项目在 Windows 上最经典的翻车点没有之一。建议顺便把 predict.py 里的 open 语句都检查一遍因为 predict.py 读 pickle 也容易忽略编码参数。5.3 现象预测时 KeyError 或者索引越界调用 predict.py 预测一句正常的话报 KeyError: 某词或者在 text_to_sequence 过程中出现 IndexError: index out of range。原因是进来的词不在 word_dict 里。真实场景下用户输入的文本可能包含训练集里没出现过的词、英文、数字、表情符号直接查字典必然抛 KeyError。越界则是 pad_sequences 时 max_len 大于词表合法索引范围可能因为未知词被默认映射到了一个超出词表大小的索引上。解决序列化时用 word_dict.get(w, 0) 的写法未知词默认走 0 填充这就是 OOVout-of-vocabulary策略。但要确认 0 没有被 train.py 占作合法词索引否则要把合法索引整体后移一位。碰到表情符号和英文串可以先用正则过滤一遍文本再做分词常见做法是 re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text)。这样预测阶段明显更稳定体验也更好。5.4 现象训练日志显示 loss 不降准确率卡在某个值train.py 打印出来的 loss 从第一轮开始几乎不动准确率一直卡在 55% 或 60% 左右不升反降。原因最可能是标签分布严重不平衡模型学会把一切都预测成样本最多的那个类其次是学习率设置偏大导致 loss 震荡还有一种可能是 max_len 太短长句信息被截断到只剩开头几个字模型几乎看不到有效特征。解决先用第 2 章的快速检查脚本看标签分布。如果某个类别占比超过 70%训练时用 class_weight 拉低多数类权重这是 Keras 的 class_weight 参数可以处理的。学习率从 0.001 降到 0.0005看 loss 曲线是否安定下来。max_len 从 16 调到 32 或 64观察验证集准确率的变化趋势。这个排查顺序基本能覆盖大部分训练不收敛的问题。5.5 现象预测结果有系统性偏向比如全是“中性”predict.py 对一组明显正负面倾向都很强的句子都输出“中性”。原因是训练数据里“中性”样本远多于正负样本模型学到了输出概率尽量靠近先验分布。也有可能数据集本身是均匀三类但模型训练不充分、embedding 维度过低导致语义区分度不够。解决除了加平衡权重另一个处理是调整输出阈值。比如把概率分布里小于 0.4 的置信度做成“置信度过低请换一句话试试”的提示这比硬输出一个错误类别更符合产品逻辑。在课程设计报告里这会成为你比同组同学多写的一个“系统优化点”。先看标签分布再训模型是真的重要这条我已经踩过不止一次。6. 把模型接进对话机器人robot_chat.py 与验证小技巧robot_chat.py 是这套资源里最有意思的一部分。它把 predict.py 封装成对话循环你在命令行输入一句话模型判断情感后给出相应回复。典型的交互流程是这样bash 你今天天气不错出去走走 机器人这句听起来是正面情绪继续保持好心情 你这家外卖等了一个小时还没到 机器人这句表达的是负面情绪需要帮你换个平台吗实现逻辑并不复杂就是读取标准输入调用情感预测再按标签映射不同回复。自己扩展的话把回复表换成你想测试的短语就能构造一个简单的情绪互动机器人也可以直接用在答辩现场做演示比干跑一个 print 结果直观得多。训练完模型之后我还习惯做一次完整的验证再写报告这里分享一个具体做法从原始数据里随机抽 100 条样本用 predict_sentiment 跑一轮统计准确率并打印错误样本。这个流程会暴露很多训练日志里看不到的问题实现也很简单python import randomwith open(data/train.json, r, encodingutf-8) as f: data json.load(f)random.seed(42) sample random.sample(data, 100) correct 0 for item in sample: pred, _ predict_sentiment(item[text]) if pred item[label]: correct 1 print(f100条样本准确率: {correct / 100:.2f})随机种子设为 42保证每次抽样的结果可复现循环里逐条比较预测标签和真实标签统计正确数。如果准确率只有六成先别急着改模型把错误样本打印出来看看是不是类别分布问题再针对漏判最多的类别补充数据效果往往立竿见影。从那以后我每次拿到一个情感分析课程设计都强制自己先花十分钟跑数据分布检查再花十分钟做一次 100 条样本的抽验最后才把预测截图贴进报告。这两个步骤看起来简单但正是它们帮我避开了大部分“训练看着挺好、一预测就翻车”的尴尬局面。希望这套源码和这些经验能帮到你跑通之后在这个基础上做自己的扩展会比从头搭模型轻松很多。本文还有配套的精品资源点击获取