简介这份资源是Python基于LSTM三分类的文本情感分析完整项目面向计算机相关专业正在做课程设计、期末大作业的学生以及需要NLP项目实战练习的学习者。项目将文本情感划分为三类采用LSTM网络建模配套数据预处理、词向量训练与模型评估流程适合作为入门深度学习的练手案例。压缩包共13个文件约11.63MB包含3个csv情感语料、3个py训练与测试脚本、2个ipynb交互式笔记以及pkl词向量模型、h5权重文件、yml配置、txt依赖清单和md说明文档覆盖从数据到模型落地的完整链路。目前已有307人学习下载。读者可获得可直接运行的源码、经导师认可的高分设计方案与文档说明借助清晰的目录结构快速理解LSTM三分类的实现思路并在此基础上完成自己的课程设计或二次开发。1. 三分类情感分析项目从数据到 LSTM 模型的一条龙落地拿到一份带lstm_train.py、lstm_test.py、Word2vec_model.pkl和lstm.h5的完整包第一反应往往不是兴奋而是先确认它到底能不能跑通。这个项目做的是中文文本三分类情感分析——把一段话判成积极、中性或消极技术栈是 Python Keras/TensorFlow 的 LSTM词向量用 Word2vec 预训练。它适合正在做课程设计、期末大作业的计算机相关专业学生也适合想跑一遍完整 NLP 流程的入门者。包里数据、训练脚本、测试脚本、已训练模型、依赖清单齐全省掉了自己从零搭 pipeline 的时间。下面按「数据怎么处理 → 模型怎么训 → 怎么避坑 → 怎么验证」的顺序拆开讲每一步都落到能直接抄的命令和参数上。2. 数据管线拆解从 neg/neutral/pos 三份 CSV 到 Word2vec 词向量2.1 三分类数据的组织方式与标签映射包里data目录下是neg.csv、neutral.csv、pos.csv三个文件这是最朴素也最不容易出错的三分类组织方式——按类别分文件存放读取时直接给每个文件打上对应标签。常见做法是读进来后拼成一个 DataFrame标签用 0/1/2 表示0 对应 neg、1 对应 neutral、2 对应 pos。这里有个容易翻车的点标签顺序必须和后面模型输出层的 softmax 维度严格对应否则训练 loss 正常下降但预测结果全反。我一般会在data deal.py里显式写一个label_map {neg: 0, neutral: 1, pos: 2}而不是靠文件遍历顺序隐式决定。import pandas as pd label_map {neg: 0, neutral: 1, pos: 2} frames [] for name, label in label_map.items(): df pd.read_csv(fdata/{name}.csv, encodingutf-8) df[label] label frames.append(df) data pd.concat(frames, ignore_indexTrue) # 打乱顺序避免同类样本连续出现影响 batch 统计 data data.sample(frac1, random_state42).reset_index(dropTrue) print(data[label].value_counts())这段代码的关键参数是encodingutf-8中文 CSV 用 gbk 读会直接报 UnicodeDecodeErrorrandom_state42保证每次打乱结果一致方便复现。value_counts()用来确认三类样本是否均衡——如果某一类只有几十条后面训练时 F1 会很难看需要先做重采样或调 class_weight。2.2 中文分词与 Word2vec 词向量的加载中文文本进 LSTM 之前必须分词项目里用的是 jiebarequirements.txt里应该有。分词后每个词映射成 Word2vec 向量Word2vec_model.pkl就是预训练好的词向量模型。加载时用gensim.models.Word2vec.load()然后构建词表vocab和对应的 embedding 矩阵。这里有个血泪经验Word2vec 模型的词表大小和 LSTM 的 embedding 层输入维度必须一致否则加载权重时直接 shape mismatch。import jieba import numpy as np from gensim.models import Word2Vec w2v Word2Vec.load(Word2vec_model.pkl) vocab {word: idx 1 for idx, word in enumerate(w2v.wv.index_to_key)} # 0 留给 padding embedding_dim w2v.vector_size def text_to_seq(text, max_len100): words jieba.lcut(str(text)) seq [vocab.get(w, 0) for w in words][:max_len] return seq [0] * (max_len - len(seq)) # 不足补零 # 构建 embedding 矩阵 embedding_matrix np.zeros((len(vocab) 1, embedding_dim)) for word, idx in vocab.items(): embedding_matrix[idx] w2v.wv[word]max_len100是截断长度超过的截掉、不足的补零这个值要根据你数据里句子长度的分布来定——太短丢信息太长浪费显存。vocab.get(w, 0)里 0 是未知词的占位对应 embedding 矩阵第 0 行全零。embedding 矩阵构建完后在模型里用Embedding(vocab_size, embedding_dim, weights[embedding_matrix], trainableFalse)加载trainableFalse表示冻结词向量不参与训练小数据集上这样更稳。2.3 训练集/测试集切分与序列填充切分比例常见 8:2 或 7:3用train_test_split时记得加stratifyy保证训练集和测试集里三类比例一致。序列填充用pad_sequences比手写补零更规范它支持paddingpost后面补和truncatingpost后面截和上面手写逻辑等价但更省心。from sklearn.model_selection import train_test_split from tensorflow.keras.preprocessing.sequence import pad_sequences X [text_to_seq(t) for t in data[text]] y data[label].values X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) X_train pad_sequences(X_train, maxlen100, paddingpost, truncatingpost) X_test pad_sequences(X_test, maxlen100, paddingpost, truncatingpost)stratifyy是这里最容易被忽略的参数不加的话如果数据本身不均衡测试集可能某一类一条都没有评估结果完全不可信。paddingpost和truncatingpost要和训练时保持一致否则同一句话在训练和推理时被处理成不同长度预测结果会飘。3. LSTM 模型搭建与训练lstm_train.py 里的关键参数3.1 模型结构Embedding LSTM 三分类 Softmaxlstm_train.py里的模型结构不复杂典型的三层Embedding 层加载预训练词向量LSTM 层提取序列特征Dense 层输出三个类别的概率。LSTM 的units一般设 64 或 128dropout和recurrent_dropout用来防过拟合。输出层用Dense(3, activationsoftmax)损失函数用categorical_crossentropy标签是 one-hot或sparse_categorical_crossentropy标签是整数。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout model Sequential([ Embedding(len(vocab) 1, embedding_dim, weights[embedding_matrix], trainableFalse), LSTM(128, dropout0.3, recurrent_dropout0.3), Dense(64, activationrelu), Dropout(0.4), Dense(3, activationsoftmax) ]) model.compile( losssparse_categorical_crossentropy, optimizeradam, metrics[accuracy] ) model.summary()trainableFalse冻结 embedding小数据集上能防止词向量被带偏。LSTM(128)的 128 是隐藏单元数数据量小的话可以降到 64训练更快也不容易过拟合。dropout0.3作用在输入门上recurrent_dropout0.3作用在循环连接上两个都设能明显压住过拟合但recurrent_dropout会拖慢训练速度GPU 上还好CPU 上可能慢一倍。sparse_categorical_crossentropy对应整数标签不用手动做 one-hot省一步。3.2 训练循环batch_size、epochs 与 EarlyStopping训练时batch_size一般设 32 或 64epochs设 20 到 50配合EarlyStopping在验证 loss 不降时提前停。validation_split0.1从训练集里再切 10% 做验证不占用测试集。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue), ModelCheckpoint(lstm.h5, monitorval_loss, save_best_onlyTrue) ] history model.fit( X_train, y_train, batch_size64, epochs30, validation_split0.1, callbackscallbacks )patience3表示验证 loss 连续 3 个 epoch 不降就停restore_best_weightsTrue保证最后留下的是验证 loss 最低那轮的权重而不是最后一轮的。ModelCheckpoint把最优模型存成lstm.h5这个文件就是包里已经提供的那个——如果你自己重训会覆盖它建议先备份。训练完在测试集上model.evaluate(X_test, y_test)看准确率三分类任务上 80% 以上算正常低于 70% 要回头查数据标签和分词。3.3 模型保存与加载h5 格式的兼容性注意lstm.h5是 Keras 的 HDF5 格式加载用tf.keras.models.load_model(lstm.h5)。这里有个版本坑不同 TensorFlow 版本保存的 h5 模型加载时可能报Unknown layer或mismatched keys。常见做法是确认requirements.txt里的 TF 版本和你本地一致或者用custom_objects显式传入自定义层。如果只是做推理lstm_test.py里已经写好了加载和预测流程直接跑就行。from tensorflow.keras.models import load_model model load_model(lstm.h5) pred model.predict(X_test[:5]) print(pred.argmax(axis1)) # 0/1/2 对应 neg/neutral/posargmax(axis1)取概率最大的类别索引再映射回{0: neg, 1: neutral, 2: pos}就是最终结果。注意predict返回的是概率分布不是标签别直接当类别用。4. 避坑与排查跑通这份源码最容易翻车的五个地方4.1 现象训练 loss 一直不降准确率卡在 33%原因通常是标签和模型输出维度对不上或者 embedding 矩阵加载时词表索引错位。三分类随机猜就是 33%如果准确率一直贴着这个数基本可以断定模型没学到东西。解决方法是先打印y_train的分布确认三类都有再检查embedding_matrix的行数是否等于len(vocab) 1以及vocab里索引是否从 1 开始0 留给 padding。4.2 现象加载 lstm.h5 报错「Unknown layer: LSTM」这是 TensorFlow 版本不匹配的典型表现。包里requirements.txt如果写的是tensorflow2.x而你本地装的是 2.yh5 里的层配置可能读不出来。解决方法是按requirements.txt建虚拟环境或者用tf.keras.models.load_model(lstm.h5, compileFalse)先跳过编译再手动compile。4.3 现象中文 CSV 读取报 UnicodeDecodeErrorneg.csv等文件如果是在 Windows 上用 Excel 保存的编码可能是 gbk 而不是 utf-8。解决方法是pd.read_csv(..., encodinggbk)或者用chardet检测编码后再读。更稳的做法是统一转成 utf-8 再存一份避免后续 jieba 分词时再出编码问题。4.4 现象预测结果全是同一类模型塌缩到只预测多数类通常是因为数据严重不均衡且没做处理。解决方法是先看value_counts()如果某一类占比超过 70%要么对少数类过采样要么在fit里加class_weight。另外检查max_len是不是设得太短导致句子被截得只剩几个词信息量不够。4.5 现象jieba 分词后大量词不在 vocab 里Word2vec_model.pkl的词表是固定的如果测试文本里有很多新词网络用语、专业术语vocab.get(w, 0)会大量返回 0等于这些词全被当成未知。解决方法是确认训练数据和测试数据的分词口径一致必要时用更大的语料重新训 Word2vec或者在分词后加一层同义词映射。5. 进阶验证用混淆矩阵和单句推理确认模型真的可用跑通训练只是第一步要确认模型不是「假会」得看混淆矩阵和单句推理。混淆矩阵能告诉你三类里哪一类最容易混——通常 neutral 和 pos/neg 的边界最模糊因为中性文本本身特征就弱。from sklearn.metrics import confusion_matrix, classification_report import numpy as np y_pred model.predict(X_test).argmax(axis1) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred, target_names[neg, neutral, pos]))classification_report里的 precision/recall/f1 比单纯看 accuracy 有用得多。如果 neutral 的 recall 明显低说明模型把中性样本误判成了积极或消极这时候可以考虑加一层注意力机制或者对 neutral 类做数据增强。单句推理则是最直观的验证方式def predict_sentiment(text): seq pad_sequences([text_to_seq(text)], maxlen100, paddingpost, truncatingpost) prob model.predict(seq)[0] labels [neg, neutral, pos] return labels[prob.argmax()], prob.max() print(predict_sentiment(这个产品质量很好物流也快)) print(predict_sentiment(一般般吧没什么特别的)) print(predict_sentiment(太差了用了一次就坏了))三句话分别对应积极、中性、消极如果输出符合预期说明模型确实学到了语义特征而不是靠标签分布蒙的。我一般还会故意喂几句带反讽的文本比如「真是太好了用了三天就报废」看模型会不会翻车——翻车很正常这能帮你判断模型的边界在哪。从那以后我每次拿到这类情感分析项目都会先跑一遍混淆矩阵再跑三句单句推理两个都过了才敢说「能用」。希望帮到你。本文还有配套的精品资源点击获取