LSTM影评情感分类全流程:数据预处理、模型训练与报告复现
2026/9/22 10:34:55 网站建设 项目流程

简介:一套基于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, lang="zh"): 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, lang="zh") x_train, x_val, y_train, y_val = train_test_split( df["text"], df["label"], test_size=0.2, stratify=df["label"], random_state=42 ) tokenizer = Tokenizer(num_words=20000, oov_token="<OOV>") 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, maxlen=200, padding="post", truncating="post") x_val = pad_sequences(x_val, maxlen=200, padding="post", truncating="post")

第一段clean_text处理原始文本:HTML标签被正则去掉,[^\w\u4e00-\u9fa5]把标点替换成空格,英文再统一小写;中文在保留中文和英文字符的基础上用jieba分词。之所以要把分词结果用空格连接,是因为Tokenizer默认按空格切词。train_test_split里加stratify=df["label"],确保训练集和验证集的正负样本比例与原数据一致。

Tokenizer参数中,num_words=20000表示词表上限,出现频率最高的19999个词加上OOV占一个位置;oov_token="<OOV>"会把词表外的词统一编成1号索引,避免预测时出现未知词错误。pad_sequences的maxlen=200把序列统一到200个位置,短句用0填充,padding="post"表示0补在句尾,truncating="post"表示超长时截断后半段。这两个参数保持一致,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_sequences=False表示只返回最后时刻的隐藏状态,这个向量被视为整句语义的汇总,正是分类需要的;如果后面还要再叠一层LSTM,前一层才需要True。

单向LSTM按时间顺序读句子,已经能覆盖大多数影评情感分类场景。双向LSTM会再反向读一遍,能捕获倒装和句末强调,但训练时间接近加倍。Hochreiter与Schmidhuber的原始论文里讨论的核心是门控机制如何递推更新记忆细胞,不是靠单纯堆层来提升效果;大作业答辩时能把单层LSTM的门控过程讲清楚,比直接甩一个多层双向模型更站得住脚。

dropout作用于输入到隐藏层的连接,recurrent_dropout作用于循环连接。通常两者都设0.2到0.4;注意recurrent_dropout在部分GPU实现里会拖慢训练,本地CPU跑课程作业区别不大。不要只设dropout而不设recurrent_dropout,循环网络的过拟合主要来自时间步之间的状态反复传递。

3.3 输出层与损失函数:二分类时别用softmax

from 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_dim=vocab_size, output_dim=128, mask_zero=True, input_length=max_len), LSTM(units=128, dropout=0.3, recurrent_dropout=0.3, return_sequences=False), Dropout(0.3), Dense(1, activation="sigmoid") ]) model.compile( loss="binary_crossentropy", optimizer=Adam(learning_rate=1e-3), metrics=["accuracy", "AUC"] ) model.summary()

Embedding里mask_zero=True很关键。padding产生的0值不该参与LSTM计算,mask的作用就是让模型跳过这些位置;如果不开mask,LSTM会把这些无效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够用,数据量大可到256
LSTM.units64~128隐藏状态维度,参数量随units近似平方增长
LSTM.dropout / recurrent_dropout0.2~0.4两类dropout要同时开,只开一个会漏掉循环过拟合
return_sequencesFalse最后一层LSTM输出最终时刻状态,分类任务用False
Dense(1, sigmoid)1个神经元二分类输出一个概率,阈值默认0.5
lossbinary_crossentropy与sigmoid配套,多分类再换categorical
optimizerAdam(1e-3)loss震荡优先降学习率,不要先改网络层数

4. LSTM训练加速与过拟合处理:早停、学习率与类权重

模型编译完,训练环节最常见的问题是:训练accuracy一路逼近100%,验证loss先降后升。这不是代码bug,而是LSTM把训练集里的评论细节背下来了。要避免这种情况成为答辩扣分点,需要靠回调在验证集表现变差时及时踩刹车。

4.1 训练配置与回调函数

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau callbacks = [ EarlyStopping(monitor="val_loss", patience=3, restore_best_weights=True), ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=1, min_lr=1e-5), ModelCheckpoint("best_model.keras", monitor="val_accuracy", save_best_only=True) ] history = model.fit( x_train, y_train, validation_data=(x_val, y_val), epochs=20, batch_size=128, callbacks=callbacks, verbose=1 )

epochs设20只是上限,EarlyStopping会在验证集loss连续3轮不下降时停止训练。restore_best_weights=True是最容易漏掉的一项:不设置的话,模型停止时停在最后一轮权重,而最后一轮往往已经过拟合;设置后会自动回滚到验证指标最好的那组参数。ReduceLROnPlateau检测到val_loss连续1轮不降,就把学习率乘0.5,让Adam在损失曲面窄谷附近更平滑地收敛。ModelCheckpoint在每个epoch后对比val_accuracy,只有比历史最好成绩更高才保存,这样best_model.keras里始终是整个训练过程的最优模型。

batch_size影响梯度噪声和训练速度。128是影评数据集的稳定起点,改成32有可能把精度往上推一点,但每个epoch耗时明显增加。epochs不需要追求多,LSTM在影评这类中等规模文本上通常十轮以内就能收敛,真正有效的是早停而不是硬跑到指定轮数。

回调关键参数作用
EarlyStoppingmonitor="val_loss", patience=3连续3轮无改善就停止,回滚最优权重
ReduceLROnPlateaufactor=0.5, patience=1验证loss停滞时学习率减半,减少震荡
ModelCheckpointsave_best_only=True只在指标提升时保存,避免覆盖最优模型

4.2 评估:混淆矩阵、precision/recall与AUC

from sklearn.metrics import classification_report, confusion_matrix y_true = np.asarray(y_val) pred_proba = model.predict(x_val, batch_size=256).reshape(-1) pred_label = (pred_proba >= 0.5).astype(int) print(classification_report(y_true, pred_label, digits=4)) 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 python=3.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_pretrained=True) sample = ["这部电影的前半段很平淡,但结局把情绪完全托起来了。"] seq = tokenizer.texts_to_sequences(sample) seq = tf.keras.preprocessing.sequence.pad_sequences( seq, maxlen=200, padding="post", truncating="post" ) pred = model.predict(seq, verbose=0)[0][0] print(f"预测概率: {pred:.4f}", "正面" if pred > 0.5 else "负面")

这个脚本不追求性能,只验证“模型能加载、文本能走完预处理、输出有判别力”。把它放在项目根目录,答辩前执行一次,任何预处理函数改名、词表文件缺失或维度不匹配都能提前暴露。答辩时再运行同一个脚本,把输出和报告中的实验截图对齐,源码和报告就落在同一个可复现的结果上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询