简介一套基于LSTM的影评情感分类项目源码与报告适合计算机相关专业学生用于课程设计、期末大作业也适合正在学习深度学习文本分类的开发者参照实践。项目覆盖从文本预处理、word2vec词向量训练到LSTM模型构建、训练与评估的完整流程并配有实训报告、模型文件、训练配置及数据集处理脚本可帮助使用者快速理清情感分类任务的关键环节。压缩包共22个文件主要包括Python脚本、JSON配置、PNG结果图、Arrow数据文件、PDF报告及Markdown笔记等整体约30.85MB文件组织有序便于按模块查阅。该资源已有129人浏览学习项目曾获98分的高分评价代码与报告相互对应适合需要完整项目经验的学习者下载参考。1. LSTM影评情感分类课程作业里的硬骨头一旦开始调LSTM影评情感分类你会很快意识到真正拉开作业评分的不是网络层数而是进模型前的文本质量。基于LSTM的影评情感分类目标很直接输入一段影评输出积极或消极。它要求同时处理文本清洗、词表构建、序列填充、模型训练与指标评估恰好覆盖一门课程期末大作业最常考察的完整链条。这篇按数据预处理-模型设计-训练评估-报告复现的顺序展开给出可直接改写成自己作业的代码与参数依据。适合正在准备期末大作业、准备用LSTM完成影评情感分类并需要一份可复现源码与高分报告的人。2. 从影评文本到LSTM输入数据清洗与序列编码大多数课程项目失败在第一步直接把原始文字丢进Tokenizer。影评来自用户评论句子长度差异极大夸赞和讽刺的措辞混在一起还常夹杂HTML标签与重复标点。LSTM设计上能保留长期依赖但只有输入序列稳定、词表可控时遗忘门和记忆细胞的更新才有意义。2.1 数据源与标签分布先看类别是否失衡常见做法是选择IMDb电影评论或者中文平台爬取的影评数据。IMDb的好处是Keras内置了数据集下载解压后直接得到整数序列适合快速验证LSTM结构缺点是没有真实爬虫数据里常见的那层清洗干扰。如果作业要求自备数据CSV里至少要有text和label两列label用0和1表示负面与正面。拿到数据不要直接跑模型先用Counter统计标签分布from collections import Counter print(Counter(df[label]))输出形如Counter({0: 12500, 1: 12500})时正负样本各半后续训练指标可以放心看accuracy。如果某一类占比悬殊到9:1模型很容易学到“永远输出多数类”的捷径accuracy虚高但没有任何实用价值。这种情况下报告里应当先写清类别分布训练时再给少数类提高loss权重。2.2 清洗与分词停用词不需要全删英文影评的清洗流程比较固定去HTML标签、统一小写、把标点替换成空格。中文影评则需要先分词常见做法是jieba.cut分词后再用空格连接交给Tokenizer处理。注意不要在做分词前把停用词全部删掉“not”“no”“差一点”这类词对情感判断是关键信息LSTM需要看到否定词才能正确翻转句子的情感极性。词频截断比传统停用词表更有效。Tokenizer在构建词表时只保留出现频率最高的前num_words个词低频词和拼写错误自动被丢到词表外同时设置oov_token给未登录词保留一个统一编号。后续预测阶段就算冒出一个训练时没见过的词也不会让程序崩溃。2.3 用Tokenizer建词索引用pad_sequences对齐序列import re import jieba import numpy as np import pandas as pd from sklearn.model_selection import train_test_split from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences def clean_text(text, langzh): text re.sub(r[^], , text) text re.sub(r[^\w\u4e00-\u9fa5], , text) if lang zh: return .join(jieba.cut(text)) return text.lower() df pd.read_csv(reviews.csv) df[text] df[text].apply(clean_text, langzh) x_train, x_val, y_train, y_val train_test_split( df[text], df[label], test_size0.2, stratifydf[label], random_state42 ) tokenizer Tokenizer(num_words20000, oov_tokenOOV) tokenizer.fit_on_texts(x_train) x_train tokenizer.texts_to_sequences(x_train) x_val tokenizer.texts_to_sequences(x_val) x_train pad_sequences(x_train, maxlen200, paddingpost, truncatingpost) x_val pad_sequences(x_val, maxlen200, paddingpost, truncatingpost)第一段clean_text处理原始文本HTML标签被正则去掉[^\w\u4e00-\u9fa5]把标点替换成空格英文再统一小写中文在保留中文和英文字符的基础上用jieba分词。之所以要把分词结果用空格连接是因为Tokenizer默认按空格切词。train_test_split里加stratifydf[label]确保训练集和验证集的正负样本比例与原数据一致。Tokenizer参数中num_words20000表示词表上限出现频率最高的19999个词加上OOV占一个位置oov_tokenOOV会把词表外的词统一编成1号索引避免预测时出现未知词错误。pad_sequences的maxlen200把序列统一到200个位置短句用0填充paddingpost表示0补在句尾truncatingpost表示超长时截断后半段。这两个参数保持一致LSTM读到的永远是“句子开头在前”的完整顺序。max_len不是拍脑袋定的。先跑一下长度分布seq_lens [len(seq) for seq in tokenizer.texts_to_sequences(x_train)] for q in [50, 80, 90, 95, 99]: print(q, np.percentile(seq_lens, q))输出50、80、90、95、99共五个分位数后取95分位作为max_len。比如95%的训练影评不超过260词就把max_len设为260取太短会丢掉结尾的情绪转折取得过长会让大量样本被填充成近乎全0的向量白白增加计算量LSTM还容易在这些冗余位置上记住无意义的模式。下表是这套流程里最常见的三个参数参数常见取值调整方向num_words20000语料大、类别复杂时可提到50000但Embedding参数会同步增加max_len150~300以训练集长度分布的95分位为准padding / truncatingpost / post保持两边一致统一保留句子开头信息2.3.1 验证集不能参与词表统计Tokenizer只在训练集上fit验证集和测试集只调用texts_to_sequences。原因是验证集承担“模拟未来数据”的角色如果验证集的词频参与了词表构建会泄露高频词信息导致验证指标虚高。处理顺序永远是先fit训练集再转换训练集、验证集和测试集测试集在模型定稿之前不能参与任何统计操作包括词表、max_len和标签分布分析。3. LSTM网络结构设计与关键参数选择预处理完成后下一步是搭模型。LSTM能记住较远的信息是因为内部有门控机制遗忘门读取上一时刻的隐藏状态与当前输入决定从记忆细胞里丢掉多少信息输入门写入新信息输出门控制把多少记忆暴露给当前输出。这个“门控记忆细胞”的结构匹配影评前面铺垫、结尾转折的句式特点。3.1 Embedding层嵌入维度不是越大越好Embedding层本质是一张可训练的查找表每个词在训练过程中被更新成固定维度的稠密向量。input_dim必须与Tokenizer的num_words保持一致否则加载词索引时会越界output_dim决定每个词的向量长度影评任务128维通常够用语料规模很大时可以开到256。维度继续往上加只是让查找表参数变多并不线性提升语义表达能力反而更容易过拟合。常见做法是随机初始化Embedding让它在训练中自己学习语义。也可以载入GloVe或腾讯词向量作为初值再finetune但课程作业里引入预训练向量会增加环境依赖和加载时间除非报告专门讨论“预训练表示对影评情感分类的影响”否则不推荐在最后阶段临时加这一层。3.2 LSTM层units、dropout与return_sequences怎么定LSTM层的units是隐藏状态维度。影评分类取64到128比较常见units翻倍会让模型参数量近似翻四倍训练样本只有几万条时选64更稳数据充足再上128。return_sequencesFalse表示只返回最后时刻的隐藏状态这个向量被视为整句语义的汇总正是分类需要的如果后面还要再叠一层LSTM前一层才需要True。单向LSTM按时间顺序读句子已经能覆盖大多数影评情感分类场景。双向LSTM会再反向读一遍能捕获倒装和句末强调但训练时间接近加倍。Hochreiter与Schmidhuber的原始论文里讨论的核心是门控机制如何递推更新记忆细胞不是靠单纯堆层来提升效果大作业答辩时能把单层LSTM的门控过程讲清楚比直接甩一个多层双向模型更站得住脚。dropout作用于输入到隐藏层的连接recurrent_dropout作用于循环连接。通常两者都设0.2到0.4注意recurrent_dropout在部分GPU实现里会拖慢训练本地CPU跑课程作业区别不大。不要只设dropout而不设recurrent_dropout循环网络的过拟合主要来自时间步之间的状态反复传递。3.3 输出层与损失函数二分类时别用softmaxfrom tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam vocab_size 20000 max_len 200 model Sequential([ Embedding(input_dimvocab_size, output_dim128, mask_zeroTrue, input_lengthmax_len), LSTM(units128, dropout0.3, recurrent_dropout0.3, return_sequencesFalse), Dropout(0.3), Dense(1, activationsigmoid) ]) model.compile( lossbinary_crossentropy, optimizerAdam(learning_rate1e-3), metrics[accuracy, AUC] ) model.summary()Embedding里mask_zeroTrue很关键。padding产生的0值不该参与LSTM计算mask的作用就是让模型跳过这些位置如果不开maskLSTM会把这些无效0向量也当作真实输入更新一次白白引入噪声。后面的Dropout层在Keras中会继续传递mask所以放在LSTM之后不会破坏屏蔽效果。输出层用1个神经元加sigmoid配合binary_crossentropy满足二分类情感判断。不需要为了“看起来对称”改成2个神经元加softmax那样要把标签转成one-hot再换categorical_crossentropy结果没有本质差别却多出一段报告里很难解释清楚的转换逻辑。optimizer直接用Adam学习率从1e-3开始。如果loss震荡不降再把学习率降到5e-4或3e-4。metrics建议写成[accuracy, AUC]两个指标AUC评估的是模型在不同阈值下的排序能力比单看accuracy更能说明LSTM学到了情感差异。3.3.1 模型参数速查表参数推荐值说明Embedding.output_dim128影评词表规模下128够用数据量大可到256LSTM.units64~128隐藏状态维度参数量随units近似平方增长LSTM.dropout / recurrent_dropout0.2~0.4两类dropout要同时开只开一个会漏掉循环过拟合return_sequencesFalse最后一层LSTM输出最终时刻状态分类任务用FalseDense(1, sigmoid)1个神经元二分类输出一个概率阈值默认0.5lossbinary_crossentropy与sigmoid配套多分类再换categoricaloptimizerAdam(1e-3)loss震荡优先降学习率不要先改网络层数4. LSTM训练加速与过拟合处理早停、学习率与类权重模型编译完训练环节最常见的问题是训练accuracy一路逼近100%验证loss先降后升。这不是代码bug而是LSTM把训练集里的评论细节背下来了。要避免这种情况成为答辩扣分点需要靠回调在验证集表现变差时及时踩刹车。4.1 训练配置与回调函数from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau callbacks [ EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience1, min_lr1e-5), ModelCheckpoint(best_model.keras, monitorval_accuracy, save_best_onlyTrue) ] history model.fit( x_train, y_train, validation_data(x_val, y_val), epochs20, batch_size128, callbackscallbacks, verbose1 )epochs设20只是上限EarlyStopping会在验证集loss连续3轮不下降时停止训练。restore_best_weightsTrue是最容易漏掉的一项不设置的话模型停止时停在最后一轮权重而最后一轮往往已经过拟合设置后会自动回滚到验证指标最好的那组参数。ReduceLROnPlateau检测到val_loss连续1轮不降就把学习率乘0.5让Adam在损失曲面窄谷附近更平滑地收敛。ModelCheckpoint在每个epoch后对比val_accuracy只有比历史最好成绩更高才保存这样best_model.keras里始终是整个训练过程的最优模型。batch_size影响梯度噪声和训练速度。128是影评数据集的稳定起点改成32有可能把精度往上推一点但每个epoch耗时明显增加。epochs不需要追求多LSTM在影评这类中等规模文本上通常十轮以内就能收敛真正有效的是早停而不是硬跑到指定轮数。回调关键参数作用EarlyStoppingmonitorval_loss, patience3连续3轮无改善就停止回滚最优权重ReduceLROnPlateaufactor0.5, patience1验证loss停滞时学习率减半减少震荡ModelCheckpointsave_best_onlyTrue只在指标提升时保存避免覆盖最优模型4.2 评估混淆矩阵、precision/recall与AUCfrom sklearn.metrics import classification_report, confusion_matrix y_true np.asarray(y_val) pred_proba model.predict(x_val, batch_size256).reshape(-1) pred_label (pred_proba 0.5).astype(int) print(classification_report(y_true, pred_label, digits4)) print(confusion_matrix(y_true, pred_label))classification_report会输出precision、recall、F1和support四列这四列在期末报告里比单行accuracy有信息量得多。precision高表示模型判成正面的大多是正面recall高表示真正的正面影评被找回来的比例高。正负样本接近时两个指标没有明显矛盾类别失衡时必须说明模型更倾向于哪种错误。可以直接在报告里写“查准率与查全率在验证集上的trade-off由最终判别阈值决定。”阈值0.5是默认值不是唯一选择。用model.predict拿到原始概率后可以试一下0.45和0.55两个阈值观察混淆矩阵变化。如果0.5下假阳性多就把阈值提高如果假阴性多就降低。这一步写进报告能体现你没有把模型当黑盒。4.3 不收敛与过拟合的排查顺序loss出现nan先检查学习率Adam(1e-3)在部分中文分词数据上可能偏大降到1e-4再试然后检查print出来的词表里是否存在异常长序列比如长度超过几万的垃圾样本。梯度裁剪clipnorm可以作为备选手段但在影评分类任务里先降学习率往往比裁剪更直接。过拟合的典型信号是训练loss持续下降、验证loss拐头向上。优先按顺序调整降低LSTM units、提高dropout、减小num_words或max_len。不要急着加正则化层也不要轻易堆第二层LSTM模型复杂度增加会让过拟合来得更早。还有一个容易忽视的问题有些项目把validation_data从测试集里切出来调参几轮后测试集信息已经被模型间接看到最后报告的测试指标会虚高。正确做法是训练集、验证集、测试集三段严格分开验证集只用来看早停和调整学习率全部实验做完后才能在测试集上跑最后一次并记录结果。这个数据集划分顺序比调出高一个点的准确率更值得写进报告。5. 面向LSTM影评分类的期末报告与可复现性验证技巧5.1 报告骨架直接对照源码目录高分报告不按课程PPT目录堆文字而是按“数据-模型-实验-复现”四条线展开。第1章写问题背景和选型依据说明影评情感分类为什么适合用LSTM第2章写数据清洗、词表分析和序列长度分布第3章写模型结构和参数选择表第4章放训练曲线、混淆矩阵和不同配置下的对比表格第5章写结论、不足与可能的改进方向。每一章都对应源码里一个文件评阅人拿到项目后可以按图索骥。实验部分放一张不同max_len下的AUC折线图比写一千字调参心得更有说服力。报告里还要画出训练与验证的loss曲线两线距离越拉越大时过拟合结论就有直接证据再配上自己调整dropout或units后的对比整个实验逻辑就是封闭的。5.2 固定随机种子与环境依赖import random import numpy as np import tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)这段固定随机种子的代码放在所有库导入之后、数据加载之前保证每次运行得到的初始化权重和数据打乱顺序一致。环境依赖建议在项目根目录执行pip freeze requirements.txt报告附录里列出Python与TensorFlow版本。很多本地能跑、答辩机器上失败的案例问题都出在numpy版本冲突或缺少jieba这类小依赖而不是模型代码本身。新机器上从零搭建环境常见做法是用conda create -n lstm_hw python3.8建一个独立虚拟环境再pip install -r requirements.txt避免把课程项目装进系统Python造成包污染。5.3 用冒烟测试自检全流程# smoke_test.py答辩前跑一遍验证全流程没有断点 import tensorflow as tf from model import build_model from preprocess import load_tokenizer tokenizer load_tokenizer() model build_model(load_pretrainedTrue) sample [这部电影的前半段很平淡但结局把情绪完全托起来了。] seq tokenizer.texts_to_sequences(sample) seq tf.keras.preprocessing.sequence.pad_sequences( seq, maxlen200, paddingpost, truncatingpost ) pred model.predict(seq, verbose0)[0][0] print(f预测概率: {pred:.4f}, 正面 if pred 0.5 else 负面)这个脚本不追求性能只验证“模型能加载、文本能走完预处理、输出有判别力”。把它放在项目根目录答辩前执行一次任何预处理函数改名、词表文件缺失或维度不匹配都能提前暴露。答辩时再运行同一个脚本把输出和报告中的实验截图对齐源码和报告就落在同一个可复现的结果上。本文还有配套的精品资源点击获取