ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

烂番茄影评情感分类实战:TensorFlow+BiLSTM完整流程

烂番茄影评情感分类实战:TensorFlow+BiLSTM完整流程 简介一份面向高校计算机相关专业学生和初学者的Python大数据与人工智能实践项目资料源自华中科技大学大作业核心任务是基于Python对烂番茄电影评论进行情感分类项目已跑通全流程答辩平均分达96分可用作课程设计、毕业设计或入门进阶的参考范例。压缩包内共555个文件约41.89MB主要包含Python源码py、实验报告PDF、训练数据表csv/tsv、NumPy向量文件npy、Visio架构图vsdx以及TensorBoard训练日志events.out.tfevents等各类数据与文档分层存放整体目录结构便于按需查阅目前已累计158人学习下载。配合实验报告和README说明可完整了解从数据预处理、模型训练到情感分类结果评估的实现思路如本地运行环境配置遇到问题作者可提供远程教学降低上手门槛。全套内容仅供学习参考请勿用于商业用途。1. 烂番茄评论情感分类一份能直接跑通的大作业源码包烂番茄Rotten Tomatoes影评情感分类是大数据与人工智能方向课程作业里少有的「数据集干净、任务明确、效果可量化」的题目每一条影评只有两个标签 fresh / rotten模型要做的是输出它属于「新鲜」好评的概率。华中科技大学这份 Python 大数据和人工智能实践大作业资源把源码、实验报告和原始数据打包在了一起代码是测试跑通之后才上传的训练过程产生的 TensorFlow 日志文件也一并保留答辩评审平均分到了 96 分。下载后先打开 README 看运行说明再跟着代码走一遍从文本清洗、词典构建到 TensorFlow 训练和评估一条完整链路可以直接复现。适合正在找人工智能大作业、大数据毕业设计参考项目的同学也适合想练 NLP 文本分类基本功的初学者。下面按我拆这套代码的顺序把数据预处理、模型选型、训练调参和踩坑点逐个讲清楚。2. 数据预处理从影评原文到干净的训练样本2.1 数据长什么样文本字段与标签烂番茄的原始数据一般是一行一条影评字段里包含评论正文和表示好坏的标签。官方影评人数据里标签是 fresh 和 rotten转成数值就是 1 和 0正好是二分类最舒服的形式。这份资源里的数据文件是 csv 格式标签列已经在文件里给好不用自己标注。拿到数据第一件事不是建模而是先看分布import pandas as pd df pd.read_csv(reviews.csv, encodingutf-8) print(df.shape) print(df.columns.tolist()) print(df[label].value_counts(normalizeTrue)) print(df.head())这段代码做的是读入数据、打印行列数、列名、标签占比和前五行。encodingutf-8必须显式写因为影评文本里混着各种特殊字符Windows 下默认按 gbk 读 utf-8 文件会直接抛UnicodeDecodeError。标签占比这一行是关键信息——如果 fresh 和 rotten 差很多后面所有阈值和指标都要重新评估。2.2 清洗函数哪些该删、哪些该留影评文本虽然比评论区干净但依然有 HTML 标签、多余空白、大小写混用和 URL。清洗的原则是「删掉不携带情感信息的噪音保留情感词和程度词」。我一般写这样一个函数import re def clean_text(text): # 去掉 HTML 标签比如 p、br text re.sub(r[^], , text) # 去掉 URL影评里经常有人贴链接 text re.sub(rhttp\S, , text) # 统一小写降低词典规模 text text.lower() # 只保留字母和空格英文影评场景下标点和数字价值不大 text re.sub(r[^a-z\s], , text) # 多个连续空格压成一个 text re.sub(r\s, , text).strip() return textre.sub(r[^], , text)把标签替换成空格而不是直接删除是为了避免word1/pword2被拼成word1word2。只保留[a-z]这一步对英文影评是划算的情感信息都藏在字母词里标点和数字对好坏的判断贡献很小删掉反而能压缩词典、减少噪音。如果以后换中文影评数据集这一步要改成 jieba 分词加停用词表处理思路一样但代码完全不同注意别照搬。清洗完统一执行并落盘后面调模型不用每次重跑df[clean_review] df[review].apply(clean_text) df.to_csv(reviews_clean.csv, indexFalse, encodingutf-8)2.3 词典与序列填充把文本变成张量模型吃不了字符串得先把文本映射成整数序列。这里有一个很容易被忽略的原则Tokenizer的词典只能从训练集统计验证集和测试集不能参与fit_on_texts否则就是数据泄漏第五章专门讲这个坑。代码是这样走的from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences MAX_VOCAB 20000 MAX_LEN 120 tokenizer Tokenizer(num_wordsMAX_VOCAB, oov_tokenOOV) tokenizer.fit_on_texts(train_texts) train_seq tokenizer.texts_to_sequences(train_texts) val_seq tokenizer.texts_to_sequences(val_texts) train_pad pad_sequences(train_seq, maxlenMAX_LEN, paddingpost, truncatingpost) val_pad pad_sequences(val_seq, maxlenMAX_LEN, paddingpost, truncatingpost)这里默认你已经按 4.1 节的方式把数据划分好了先划分再建词典这个顺序不能反反了验证集结果就不可信了。num_words20000表示只保留频率最高的两万个词其余全部映射到OOVout-of-vocabulary。这个数字对影评这种词表规模不算大的任务够用设太大模型参数量会暴涨设太小常见词会被踢出词表。MAX_LEN120是序列长度烂番茄影评的平均长度不高120 个 token 能覆盖绝大多数句子同时让批次矩阵不至于太大。paddingpost表示短句在后面补零truncatingpost表示从后面截断。方向选择会影响后面mask_zero的行为先记住这里用的是 post第五章第三坑还会回来看它。标签也要一起转成数组import numpy as np train_labels np.array(train_labels_raw, dtypefloat32) val_labels np.array(val_labels_raw, dtypefloat32)3. 模型选型与搭建TF-IDF 基线与 TensorFlow 序列模型怎么选3.1 为什么先跑一个 TF-IDF 基线拿到文本分类任务我的习惯是先跑最朴素的 TF-IDF 逻辑回归而不是直接上深度学习。理由有两个一是它几十秒就能出结果能立刻验证数据清洗有没有大问题二是它输出的特征系数可以反查哪些词贡献最大是天然的错误分析工具。这份作业里同样保留了这一步。TF-IDF 的核心思想是一个词在单篇文档里出现得多、但在整个语料里出现得少它对这篇文档的区分度就高。对比词袋Bag of WordsTF-IDF 给 the、film、movie 这类高频通用词降了权重影评这种情感词密集的文本上区分效果会明显好一截。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline pipeline make_pipeline( TfidfVectorizer(max_features20000, ngram_range(1, 2), stop_wordsenglish), LogisticRegression(C1.0, max_iter1000) ) pipeline.fit(train_texts, train_labels_raw) base_acc pipeline.score(val_texts, val_labels_raw) print(TF-IDF LR 验证集准确率:, round(base_acc, 4))ngram_range(1, 2)同时考虑单词和相邻双词能捕捉 not good、never laugh 这类否定结构单看 unigram 会丢掉这层信息。stop_wordsenglish把 the、and 这类功能词删掉因为它们在两个类别里出现频率几乎一样留着只会稀释信号。这个基线在烂番茄数据上一般能到 75%82% 的准确率它是深度学习模型的及格线——如果后面 LSTM 连这个都打不过说明训练配置有问题而不是模型结构的问题。3.2 嵌入层 BiLSTM序列模型的核心结构基线确认数据没问题之后再上序列模型。为什么影评适合序列模型而不是把 TF-IDF 特征直接丢给全连接网络因为影评的情感经常藏在上下文关系里前半句夸、后半句转折或者先抑后扬。TF-IDF 把词顺序打没了LSTM 这类循环结构按顺序读入每个词能保留局部依赖。资源里用的结构是嵌入层 双向 LSTM 两个全连接层属于文本分类的经典配置from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Bidirectional, Dense, Dropout model Sequential([ Embedding(input_dimMAX_VOCAB, output_dim128, mask_zeroTrue, input_lengthMAX_LEN), Bidirectional(LSTM(64, return_sequencesFalse)), Dropout(0.3), Dense(32, activationrelu), Dropout(0.2), Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) model.summary()Embedding层把每个词的整数索引映射成 128 维稠密向量向量在训练中持续更新让语义相近的词在向量空间里逐渐靠近。mask_zeroTrue告诉框架序列里的 0padding 补出来的位置不参与计算省算力也避免模型把空白当内容。Bidirectional(LSTM(64))让 LSTM 同时从正反两个方向读句子输出两个方向最终隐状态的拼接影评这种「结论可能在开头也可能在结尾」的场景下比单向 LSTM 稳。两层Dropout是防止过拟合的随机丢弃0.3 和 0.2 是经验值后面避坑章节会讲怎么调。return_sequencesFalse表示只保留最后一个时间步的输出分类任务用这个如果要再接一层 LSTM 才需要True。编译之后跑一遍model.summary()确认可训练参数量级在几百万这个范围。如果超过一两千万对万条级训练数据的影评任务已经偏大过拟合风险会明显升高。3.3 损失函数、优化器与评估指标怎么配二分类的默认组合就是sigmoidbinary_crossentropy最后一层输出一个 0 到 1 的概率损失函数衡量预测概率与真实标签之间的交叉熵。这里不要用categorical_crossentropy那个要求标签是 one-hot用错要么报 shape 错误要么指标含义变味。优化器直接选adam它对学习率的敏感度比 SGD 低得多默认的 0.001 起步基本不会出大问题。metrics[accuracy]监控准确率但如果知道数据集标签不均衡5.1 节的情况还要额外算AUC或者Precision/Recall否则训练曲线会上演「loss 很低、准确率很高、实际啥也没学到」的假象。4. 训练与调参把验证准确率顶上去的关键操作4.1 数据划分与泄漏检查训练前先划分顺序是先把全量数据按比例切成训练、验证、测试三块再做清洗和词典构建。清洗在划分前后做都行但词表统计必须在划分之后因为词表是全量数据的统计量一旦混入验证集信息就是泄漏。我一般用train_test_split加分层抽样from sklearn.model_selection import train_test_split train_texts, val_texts, train_labels_raw, val_labels_raw train_test_split( df[clean_review], df[label_num], test_size0.2, random_state42, stratifydf[label_num] )stratifydf[label_num]按标签比例分层抽样。如果数据里 fresh 占 60%训练集和验证集都会保持这个比例避免某一折全是同一个类别、评估结果失真。random_state42固定随机种子保证每次划分一致调参时才有的对比。注意划分之后验证集和测试集在整个实验过程中只能被「看」两次——一次是调超参数时看验证集一次是最后评估时看测试集。反复拿验证集调参到满意验证集就已经被你「记住」了。泄漏检查有个笨办法但很有效训练完之后把验证集里概率最高的几十条样本打印出来看原文如果发现高分样本恰好多是和训练集重复的句子往回查清洗和划分阶段的顺序。4.2 批次、学习率、早停与模型保存训练参数里最容易翻车的是批次大小和早停。批次太大每个 epoch 更新次数少、收敛慢批次太小梯度噪声大、loss 曲线像锯齿。对万条级影评数据batch_size64是稳妥起点显存紧往 32 降。epoch 数别死设配早停让它自己决定什么时候停from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, TensorBoard callbacks [ EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue), ModelCheckpoint(best_model.h5, monitorval_accuracy, save_best_onlyTrue), TensorBoard(log_dirlogs/run_fresh_rotten) ] history model.fit( train_pad, train_labels, validation_data(val_pad, val_labels), epochs20, batch_size64, callbackscallbacks )EarlyStopping的monitorval_loss让训练在验证损失连续 3 个 epoch 不再下降时提前终止restore_best_weightsTrue把权重回滚到验证集表现最好的位置。ModelCheckpoint按val_accuracy保存最优权重即使后面训练崩了手里也有一份最好的结果。patience我习惯从 3 起步太小训练还没发挥完就被掐太大起不到防过拟合作用。学习率用 adam 默认 0.001 起步但训练后期可以手动降。常见做法是每 5 个 epoch 乘以 0.5或者用ReduceLROnPlateau自动降from tensorflow.keras.callbacks import ReduceLROnPlateau ReduceLROnPlateau(monitorval_loss, factor0.5, patience2, min_lr1e-5)factor0.5表示触发时学习率减半min_lr给个下限防止减到零。超参数没有万能组合但可以参考这个范围起步参数推荐范围说明batch_size3264序列短64 起步显存不够降 32epochs1525配合早停一起用learning_rate1e-3 起步adam 默认后期降一半patience23验证 loss 连续 N 轮不降则停LSTM units64128影评数据量级下 64 够用4.3 TensorBoard 回调与事件文件TensorBoard(log_dirlogs/run_fresh_rotten)会把每轮训练的 loss、accuracy、val_loss、val_accuracy 写进events.out.tfevents.*文件。这份资源里带的那一堆events.out.tfevents.1671539620.DESKTOP-5S9J28F.34412.0就是这类文件。文件名每个字段都有含义中间那串数字是训练开始的 Unix 时间戳1671539620 对应 2022 年 12 月 20 日DESKTOP-5S9J28F是训练机器的 hostname34412是进程 ID最后的.0、.1、.10、.100是同一个训练进程产生的分片序号——TensorFlow 写事件文件时单个文件过大会自动开新分片。资源里能看到 34412 和 17488 两个不同的进程 ID对应两轮独立训练说明当时至少跑过两次不同配置的实验。命令行看曲线最直接tensorboard --logdir logs--logdir指定日志根目录TensorBoard 会把下面每个子目录当成一个独立实验并排对比。所以每次训练用独立子目录命名别把多轮训练写进同一个目录不然曲线缠在一起分不清谁是谁。5. 避坑指南训练与评估中五个常见翻车点以下五个坑是我自己跑类似任务时踩过的按出现频率排了序每一条都按「现象 → 原因 → 解决」给。5.1 标签不平衡导致准确率虚高现象验证集准确率显示 0.87看着很高把预测结果拉出来看正向类一个都没抓到全在预测多数类。原因烂番茄数据抽取方式不同时 fresh 和 rotten 比例可能差很多。如果 fresh 占 80%模型什么都不学、全输出 fresh 就有 80% 准确率loss 也低训练过程会出现「已经收敛」的错觉。解决先跑value_counts(normalizeTrue)看占比输出训练脚本里加混淆矩阵打印监控指标从 accuracy 换成 AUC或者同时看 precision / recall。如果比例确实悬殊给少数类加class_weight或对训练样本做欠采样。5.2 词典在全体数据上构建造成数据泄漏现象验证集效果出奇地好甚至比换新数据实测高出一大截。原因Tokenizer.fit_on_texts()写在了划分之前对全量数据调用了验证集里的稀有词提前进了词表模型相当于「见过」验证集的词分布评估结果虚高。解决严格按「先train_test_split再fit_on_texts(train_texts)」执行验证集和测试集只走texts_to_sequences。这是 NLP 任务的共同底线实验报告里最好也写一句「词表仅由训练集构建」答辩时加分。5.3 padding 方向与 mask_zero 不匹配现象加上mask_zeroTrue之后效果没变好甚至小幅下降短句子的预测结果飘忽不定。原因paddingpre把 0 加在句子前面LSTM 读到句尾时 padding 位还在参与计算或者mask_zero没开LSTM 把大量补零位置当成真实输入隐状态被无效信息污染。解决统一paddingposttruncatingpost并且Embedding层开mask_zeroTrue。注意mask_zero只有在 Embedding 层直接接循环层时有效如果中间插了Flatten或Densemask 会失效得自己写 Masking 层。5.4 训练集和验证集差距过大过拟合没拦住现象训练集准确率冲到 95% 以上验证集只有 70% 左右验证 loss 在某个 epoch 之后持续上升。原因模型容量LSTM 单元数、Embedding 维度相对万条级训练数据偏大Dropout 不够早停没开或者 patience 太大让训练跑过了头。解决先提 Dropout0.3 提到 0.5 试试没效果再降 LSTM 单元数128 降到 64然后把早停patience收到 23。修好之后看验证 loss 曲线确认拐点出现的位置和早停触发的位置对得上。5.5 Windows 中文环境下的编码与日志目录问题现象pd.read_csv报UnicodeDecodeError或者 TensorBoard--logdir logs打开后页面空白。原因数据文件是 UTF-8 编码Windows 部分环境默认按 gbk 读或者项目路径、日志目录带了中文命令行参数和 TensorBoard 解析都容易出问题。解决所有读文件的地方显式写encodingutf-8写文件同样项目目录、虚拟环境、log_dir 全部用英文命名别图省事建「实验1」「最终版」这种目录。用 vscode 配 python 环境时顺手把默认编码改成 UTF-8能少踩一半编码坑。6. 进阶验证用事件文件和混淆矩阵复盘整轮训练训练结束不是终点要敢问「模型到底学到了什么」。我最常用的复盘是两件事从事件文件里把训练历史读出来看收敛过程再把验证集预测拆到混淆矩阵里找坏 case。读事件文件不一定要开 TensorBoard脚本也能直接读from tensorflow.python.summary.summary_iterator import read_events for event in read_events(logs/run_fresh_rotten/events.out.tfevents.1671539620.DESKTOP-5S9J28F.34412.0): for value in event.summary.value: tag value.tag if loss in tag or accuracy in tag: print(event.step, tag, round(float(value.simple_value), 4))read_events的路径可以指到单个事件文件也可以指到日志目录。这段代码把每个 step 的监控标量打出来重点看val_loss是不是先降后升、拐点在哪和早停的设置对不对得上。新样本实测也要走一遍写进实验报告from tensorflow.keras.models import load_model best_model load_model(best_model.h5) new_review [A brilliant and witty film with superb acting.] seq tokenizer.texts_to_sequences(new_review) pad pad_sequences(seq, maxlenMAX_LEN, paddingpost, truncatingpost) pred best_model.predict(pad)[0][0] print(fresh 概率:, round(float(pred), 4))load_model加载的是ModelCheckpoint保存的最优权重不是训练结束那一刻的权重——如果没开restore_best_weights两者差别很大加载完务必先跑一遍验证集确认指标对得上。输出大于 0.5 判 fresh小于 0.5 判 rotten但实际部署时很多人把阈值提到 0.6把差评误判成好评的代价比把好评误判成差评高得多。从那以后我每次训练完都强制自己走一遍「事件文件读曲线 混淆矩阵找坏 case 新样本实测」三件套实验报告里这三样齐了答辩时心里才有底。这份资源里的源码、实验报告和数据正好够你把整个流程完整跑一遍希望帮到你。本文还有配套的精品资源点击获取
返回列表