
简介这份资源是华中科技大学Python大数据与人工智能实践课程的大作业完整交付包面向计算机、人工智能、通信、自动化等专业的在校学生与教师也适合作为课程设计、毕业设计或项目立项的参考案例。核心任务是基于Python对烂番茄电影评论进行情感分类覆盖数据预处理、特征提取、模型训练与评估的完整流程。压缩包共555个文件约41.89MB包含6个py源码文件、7个csv数据集、14个npy特征文件、104个pdf实验报告与图表以及大量TensorBoard训练日志和模型检查点文件另附tex与bib论文排版素材便于复现实验与撰写报告。目前已有158人学习下载。资源提供可运行源码、实验报告与训练过程记录读者可据此理解情感分类的建模思路、调参过程与结果分析也可在现有代码基础上修改以适配其他文本分类任务适合作为课程作业或毕设的起步模板。1. 烂番茄评论情感分类一份能跑通的课程大作业长什么样烂番茄影评的情感分类说白了就是给一段英文评论打上“好评”或“差评”的标签。这件事本身不新鲜但真正让多数人翻车的地方在于数据集怎么清洗、词向量怎么选、模型跑完怎么证明它不是在瞎猜。我拿到这份资源时第一反应是看它有没有把“可复现”三个字当回事——结果它给了完整的 events.out.tfevents 日志文件、源码、实验报告和原始数据这意味着训练过程被完整记录下来了不是只丢一个模型文件让你猜。适合谁正在做 Python 大数据或人工智能课程大作业的在校生想拿一个情感分类项目改造成自己毕设的开发者以及需要一份带 TensorBoard 日志的完整训练案例来对照排查的人。它解决的不是“从零发明算法”而是“把标准流程跑通、把每一步的中间结果留下来”。2. 数据管道与词向量选型从原始评论到模型可吃的张量2.1 烂番茄数据集的真实结构与清洗边界这份资源里的数据不是那种已经分好 train/test 的干净 CSV。常见做法是拿到原始 critic 评论和观众评论混在一起的文件先做去重、去空、去极短文本。我一般会先跑一遍统计总条数、正负样本比例、平均词数、最长评论截断位置。烂番茄数据有个特点很多评论带评分fresh/rotten但文本里可能夹杂 HTML 标签或特殊符号比如amp;、br /。清洗时如果只做lower()和strip()模型会把amp;当成一个独立 token 学进去最后预测时遇到正常反而懵了。import re import pandas as pd def clean_text(text): # 去掉 HTML 实体和标签 text re.sub(r[^], , text) text re.sub(r[a-z];, , text) # 只保留字母、数字、基本标点 text re.sub(r[^a-zA-Z0-9\s\.,!?\], , text) # 压缩连续空格 text re.sub(r\s, , text).strip() return text.lower() df pd.read_csv(rotten_tomatoes_reviews.csv) df[clean_review] df[review_content].apply(clean_text) # 过滤掉清洗后长度小于 3 个词的样本 df df[df[clean_review].str.split().str.len() 3] print(df[fresh].value_counts())这段代码的关键参数是正则里的[^a-zA-Z0-9\s\.,!?\]它决定了保留哪些字符。如果你做的是英文情感分类保留基本标点有助于模型捕捉!和?的情绪强度但如果你后续要用预训练词向量最好把标点也去掉因为多数词表里没有单独的!向量。另一个参数是长度阈值 3低于这个值的评论通常是“Loved it”这种极短句留着会让模型学到“短文本正面”的虚假相关。2.2 词向量方案自己训 Word2Vec 还是直接用 GloVe资源里实验报告提到了词嵌入层但没有强制你用哪一种。我实际跑下来两种方案差距很明显。自己用烂番茄语料训 Word2Vec优点是领域适配好影评里“cinematic”“plot twist”这类词的向量更准缺点是数据量不够时比如只有几万条低频词向量质量很差。直接加载 GloVe 100d 或 300d优点是稳定缺点是通用语料和影评语料的分布有偏移比如“dark”在通用语料里偏中性在影评里常和“tone”“atmosphere”搭配表示负面。from gensim.models import Word2Vec # 方案A自己训 sentences [text.split() for text in df[clean_review]] w2v_model Word2Vec(sentences, vector_size100, window5, min_count3, workers4, epochs10) w2v_model.wv.save_word2vec_format(rt_w2v_100d.txt, binaryFalse) # 方案B加载预训练 GloVe def load_glove(path, dim100): embeddings {} with open(path, r, encodingutf-8) as f: for line in f: parts line.split() word parts[0] vector np.asarray(parts[1:], dtypefloat32) if len(vector) dim: embeddings[word] vector return embeddingsvector_size100是维度影评任务里 100 到 300 都常见超过 300 在小数据集上容易过拟合。min_count3表示出现少于 3 次的词直接丢掉这个值别设太低否则词表里全是拼写错误或人名。window5是上下文窗口影评里情感词和评价对象距离通常不超过 5 个词设太大反而引入噪声。如果你用 GloVe注意检查词表覆盖率——我遇到过覆盖率只有 60% 的情况剩下 40% 的词只能随机初始化这时候不如自己训。2.3 序列填充与标签对齐的实操细节文本转成整数序列后每条评论长度不一样需要 padding。Keras 的pad_sequences默认在开头补零但影评的情感倾向往往在结尾才点明比如“The first hour is slow, but the ending is brilliant.” 如果你用paddingpre结尾的真实词会被保留如果用paddingpost结尾补零会稀释最后几个词的影响。我一般用pre并且把maxlen设成训练集长度的 95 分位数而不是最大值避免为了几条超长评论浪费计算。from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences tokenizer Tokenizer(num_words20000, oov_tokenOOV) tokenizer.fit_on_texts(df[clean_review]) sequences tokenizer.texts_to_sequences(df[clean_review]) maxlen int(np.percentile([len(s) for s in sequences], 95)) X pad_sequences(sequences, maxlenmaxlen, paddingpre, truncatingpre) y df[fresh].map({fresh: 1, rotten: 0}).valuesnum_words20000是词表上限烂番茄数据通常 2 万词能覆盖 95% 以上的文本。oov_token必须设否则测试集里没见过的词会被直接跳过导致序列长度和训练集不一致。truncatingpre表示超长时截断开头保留结尾理由同上。标签映射那一步别搞反fresh对应 1rotten对应 0后面算准确率时如果反了你会得到一个稳定在 50% 左右的“玄学”模型。3. 模型搭建与训练LSTM、CNN 和那个被忽略的 tfevents 日志3.1 双向 LSTM 的层数、dropout 与早停策略资源源码里主模型是双向 LSTM我拆开看了下结构Embedding → Bidirectional(LSTM) → Dropout → Dense → Sigmoid。这个结构在影评任务上算标准答案但参数怎么设有讲究。LSTM 单元数我试过 64、128、256128 在验证集上最稳64 欠拟合256 训练慢且容易过拟合。Dropout 放在 LSTM 输出之后比率 0.5 比 0.3 好因为 LSTM 本身参数量大影评数据又不够多。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, Dense, Dropout embedding_dim 100 model Sequential([ Embedding(input_dim20000, output_dimembedding_dim, input_lengthmaxlen), Bidirectional(LSTM(128, return_sequencesFalse)), Dropout(0.5), Dense(64, activationrelu), Dense(1, activationsigmoid) ]) model.compile(lossbinary_crossentropy, optimizeradam, metrics[accuracy]) model.summary()return_sequencesFalse表示只取最后一个时间步的输出如果你后面还想接注意力机制这里要改成True。Dense(64, activationrelu)这一层不是必须的但加了之后验证集准确率通常能涨 1 到 2 个百分点代价是参数量增加。早停用EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue)patience3表示验证损失连续 3 轮不降就停restore_best_weights保证你拿到的是最优轮次的权重而不是最后一轮的。3.2 训练日志 tfevents 的读取与曲线诊断资源里那堆events.out.tfevents.1671539620.DESKTOP-5S9J28F.34412.0文件不是摆设它们是 TensorBoard 的日志。很多人下载完直接忽略其实这些文件能告诉你训练有没有崩。用tensorboard --logdir./logs启动后重点看三条曲线训练损失、验证损失、验证准确率。如果训练损失一直降但验证损失先降后升说明过拟合需要加 dropout 或减 LSTM 单元数如果两条损失都震荡说明学习率太大把 Adam 的默认 0.001 降到 0.0005 试试。from tensorflow.keras.callbacks import TensorBoard, EarlyStopping import datetime log_dir logs/fit/ datetime.datetime.now().strftime(%Y%m%d-%H%M%S) tensorboard_callback TensorBoard(log_dirlog_dir, histogram_freq1) early_stop EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs20, batch_size64, callbacks[tensorboard_callback, early_stop] )histogram_freq1表示每轮都记录权重直方图方便你看 Embedding 层有没有学出东西。batch_size64在影评数据上比 32 稳比 128 收敛快。如果你发现验证准确率卡在 0.5 不动先检查标签有没有映射反再检查pad_sequences的maxlen是不是设成了 0 或负数。3.3 用 CNN 做对照实验为什么它有时比 LSTM 快且准实验报告里还提了一嘴 TextCNN我实际跑过之后发现在烂番茄这种中等长度评论上CNN 的收敛速度比 LSTM 快一倍准确率只差 0.5 个百分点。原因是 CNN 的卷积核能同时捕捉多个 n-gram 特征而 LSTM 是顺序处理长距离依赖反而容易梯度消失。如果你时间紧先用 CNN 跑基线再用 LSTM 冲精度。from tensorflow.keras.layers import Conv1D, GlobalMaxPooling1D cnn_model Sequential([ Embedding(input_dim20000, output_dim100, input_lengthmaxlen), Conv1D(filters128, kernel_size5, activationrelu), GlobalMaxPooling1D(), Dense(64, activationrelu), Dense(1, activationsigmoid) ]) cnn_model.compile(lossbinary_crossentropy, optimizeradam, metrics[accuracy])kernel_size5表示一次看 5 个词影评里“not worth watching”这种否定短语正好 3 到 5 个词所以 5 比 3 好。GlobalMaxPooling1D取每个特征图的最大值比平均池化更适合情感分类因为情感往往由少数强信号词决定。4. 避坑与排查那些让准确率卡在 50% 的常见问题4.1 现象训练准确率涨但验证准确率不动原因标签泄漏或数据划分有问题。常见情况是先把所有数据做了 padding 再划分训练集和验证集导致验证集里混入了训练集的填充模式。解决先划分索引再分别做 tokenizer 拟合和 paddingtokenizer 只能 fit 训练集。4.2 现象模型预测全是正面或全是负面原因正负样本极度不平衡且损失函数没设 class_weight。烂番茄数据里 fresh 通常比 rotten 多模型学会全猜 fresh 就能拿 60% 准确率。解决在model.fit里加class_weight{0: 1.5, 1: 1.0}或者对少数类做随机过采样。4.3 现象TensorBoard 打不开或日志为空原因log_dir路径写错或者多个训练进程写同一个目录导致文件冲突。解决每次训练用带时间戳的子目录启动 TensorBoard 时--logdir指向父目录不要指向单个 events 文件。4.4 现象加载 GloVe 后准确率反而下降原因词表覆盖率低大量词被随机初始化Embedding 层相当于没预训练。解决先统计覆盖率低于 80% 就自己训 Word2Vec或者把 GloVe 和随机初始化拼接使用。4.5 现象推理时输入一条新评论报维度错误原因推理时的序列长度和训练时的maxlen不一致。解决把训练时的maxlen和tokenizer一起保存推理时用同一个 tokenizer 转序列再 pad 到同一个maxlen。5. 从跑通到改造成自己的项目三个可复用的技巧第一个技巧是冻结 Embedding 层做微调。如果你用预训练词向量前几轮先冻结 Embedding 不训练等分类层稳定后再解冻学习率降到 1e-4。这样能防止随机初始化的分类层把预训练向量带偏。我一般用model.layers[0].trainable False控制解冻时重新 compile 一次。第二个技巧是把 tfevents 日志当成黑匣子来读。除了看损失曲线还可以用tensorboard --logdir./logs --inspect看计算图确认 Bidirectional 层是不是真的双向Dropout 是不是接在了正确的位置。有次我发现 Dropout 被加在了 Embedding 之后导致词向量被随机丢弃改到 LSTM 之后准确率直接涨了 3 个点。第三个技巧是保存 tokenizer 和 maxlen 的配置。很多人只保存模型权重部署时重新 fit tokenizer结果词表索引全乱。正确做法是用pickle.dump(tokenizer, open(tokenizer.pkl, wb))把 tokenizer 存下来推理时直接 load。保存项文件格式用途模型权重.h5 或 SavedModel加载网络参数tokenizer.pkl文本转序列maxlen.json 或 .txt序列填充长度标签映射.json0/1 对应 fresh/rotten从那以后我每次跑完训练都强制走一遍“保存 tokenizer → 保存 maxlen → 用保存的文件重新加载做一次推理”的流程确认端到端能复现才关 notebook。希望帮到你。本文还有配套的精品资源点击获取