简介:本资源为基于Python与深度学习的电影评论情感分析系统完整项目包,面向自然语言处理初学者、课程设计者及希望掌握文本情感分类实战的开发者。系统覆盖数据预处理、特征提取、模型训练与评估、图形界面部署全流程,可帮助读者理解中文评论清洗、分词、去停用词、词性标注等关键环节,并对比CNN、RNN、LSTM等网络结构在情感极性预测中的表现。压缩包共293个文件,约127.96MB,包含23个py源码、20个pyc缓存、8个npy与4个pkl模型数据、1个pb计算图,以及17个html、34个js、30个css等前端资源,另有sql、docx、pptx等文档与配置,目录结构清晰,便于按模块查阅。已有84人学习下载,适合作为课程设计、毕业项目或情感分析入门参考,可快速复现完整实验链路并理解模型评估指标的实际应用。
1. 电影评论情感分析系统:从数据到推理的完整落地路径
电商平台每天产生海量用户评论,运营团队不可能靠人工逐条阅读来判断口碑走向。一个能自动识别评论正负情感的 Python 系统,就成了内容运营、产品迭代和舆情监控的基础设施。这个标题指向的正是这样一套完整方案:用深度学习模型对电影评论文本做二分类(正面/负面),并封装成可调用的分析系统。它适合有 Python 基础、想跑通一个 NLP 落地项目的开发者,也适合需要快速搭建评论分析能力的产品团队。核心链路包括数据采集与清洗、中文分词与向量化、模型训练与调优、推理接口封装四个环节。下面按实际搭建顺序拆解,每一步都给出可复现的代码和参数说明。
2. 数据准备与中文评论清洗:把原始文本变成模型能吃的格式
2.1 评论数据从哪来、怎么存
做情感分析,第一步永远是数据。电影评论的常见来源有两类:公开数据集和自行采集。公开数据集里,中文情感分析常用的是 ChnSentiCorp(酒店评论)和 IMDb 中文翻译版,但电影领域更贴合的往往是豆瓣短评或猫眼评论。如果选择自行采集,用 requests + BeautifulSoup 抓取公开页面即可,注意控制频率、遵守目标站点的 robots 协议。
数据存储建议用 CSV 或 SQLite,字段至少包含review(评论文本)和label(0 负面 / 1 正面)。下面是一个把原始数据整理成标准格式的脚本:
import pandas as pd import re def clean_text(text): """清洗单条评论:去 HTML 标签、去 URL、去多余空白""" text = re.sub(r'<[^>]+>', '', text) # 去 HTML 标签 text = re.sub(r'http[s]?://\S+', '', text) # 去 URL text = re.sub(r'\s+', ' ', text).strip() # 合并空白 return text # 读取原始数据,假设已有两列:review, label df = pd.read_csv('raw_reviews.csv') df['review'] = df['review'].astype(str).apply(clean_text) df = df[df['review'].str.len() >= 5] # 过滤过短评论 df = df.drop_duplicates(subset=['review']) # 去重 df.to_csv('clean_reviews.csv', index=False) print(f'清洗后样本数:{len(df)},正负比例:{df["label"].mean():.2f}')这段代码做了四件事:去 HTML 标签、去 URL、合并空白字符、过滤长度小于 5 的评论并去重。label列的均值反映正样本占比,如果偏离 0.5 太多,后续训练需要做类别加权。参数上,长度阈值 5 是经验值,太短如“好”“差”缺乏上下文,太长如超过 500 字建议截断,因为后续模型有最大长度限制。
2.2 中文分词与停用词处理
中文和英文不同,没有天然空格分隔,必须先分词。常用工具是 jieba,速度快、社区活跃。停用词表可以用哈工大停用词表或百度停用词表,去掉“的”“了”“是”这类高频但无情感指向的词。
import jieba # 加载停用词 with open('stopwords.txt', 'r', encoding='utf-8') as f: stopwords = set([line.strip() for line in f]) def tokenize(text): """分词并去停用词,返回空格拼接的字符串""" words = jieba.lcut(text) words = [w for w in words if w not in stopwords and len(w) > 1] return ' '.join(words) df = pd.read_csv('clean_reviews.csv') df['tokens'] = df['review'].apply(tokenize) df.to_csv('tokenized_reviews.csv', index=False)jieba.lcut返回列表,len(w) > 1过滤单字,因为单字在情感分析中噪声较大。分词后的结果用空格拼接,是为了后续用 Keras 的 Tokenizer 或 torchtext 做词表映射。注意:停用词表不要盲目全用,像“不”“没”这类否定词必须保留,否则“不好”会被拆成“好”,情感直接反转。我一般会手动检查停用词表,把否定词和程度副词(很、非常、太)从表里删掉。
2.3 标签质量检查与数据划分
标签噪声是情感分析翻车的头号原因。常见问题是:三星中评被标成正面或负面、反讽评论被误标、同一用户刷多条相似评论。处理办法是抽样人工复核,至少抽 200 条看一遍。划分训练集/验证集/测试集按 8:1:1,用 stratified 保证正负比例一致。
from sklearn.model_selection import train_test_split train, temp = train_test_split(df, test_size=0.2, stratify=df['label'], random_state=42) val, test = train_test_split(temp, test_size=0.5, stratify=temp['label'], random_state=42) train.to_csv('train.csv', index=False) val.to_csv('val.csv', index=False) test.to_csv('test.csv', index=False) print(f'训练集 {len(train)},验证集 {len(val)},测试集 {len(test)}')stratify=df['label']是关键参数,保证划分后各集合正负比例与原始一致。random_state=42固定随机种子,方便复现。如果数据量小于 5000 条,建议用交叉验证代替固定验证集,否则验证集太小,指标波动大。
3. 深度学习模型选型与训练:TextCNN 还是 BiLSTM
3.1 为什么先试 TextCNN 而不是 BERT
电影评论情感分析属于短文本分类,句子长度通常在 10 到 100 字之间。这个场景下,TextCNN 和 BiLSTM 是性价比最高的选择。TextCNN 用不同尺寸的卷积核捕捉 n-gram 特征,训练快、参数少,在 1 万条数据上几分钟就能跑完一轮。BiLSTM 能捕捉长距离依赖,但训练慢,且短文本上优势不明显。BERT 效果好,但需要 GPU 和大量显存,推理延迟也高,如果只是做一个评论分析系统,先用 TextCNN 跑通基线,再考虑要不要上 BERT。
选型判断标准:数据量小于 5 万条、没有 GPU、要求推理速度小于 100ms,选 TextCNN;数据量大于 10 万条、有 GPU、追求最高准确率,选 BERT 微调。我一般会先用 TextCNN 跑一个基线,看准确率能不能到 85% 以上,能到就不折腾了。
3.2 用 Keras 搭一个 TextCNN 并训练
下面是一个完整的 TextCNN 实现,用 Keras 的 Tokenizer 做词表映射,Embedding 层做词向量,然后三个不同尺寸的 Conv1D 并行提取特征。
import numpy as np import pandas as pd from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Embedding, Conv1D, GlobalMaxPooling1D, Concatenate, Dense, Dropout MAX_WORDS = 20000 # 词表最大容量 MAX_LEN = 100 # 每条评论截断/填充到 100 个词 EMBED_DIM = 128 # 词向量维度 train = pd.read_csv('train.csv') val = pd.read_csv('val.csv') tokenizer = Tokenizer(num_words=MAX_WORDS, oov_token='<OOV>') tokenizer.fit_on_texts(train['tokens']) X_train = pad_sequences(tokenizer.texts_to_sequences(train['tokens']), maxlen=MAX_LEN, padding='post', truncating='post') X_val = pad_sequences(tokenizer.texts_to_sequences(val['tokens']), maxlen=MAX_LEN, padding='post', truncating='post') y_train = train['label'].values y_val = val['label'].values def build_textcnn(): inputs = Input(shape=(MAX_LEN,)) x = Embedding(MAX_WORDS, EMBED_DIM, input_length=MAX_LEN)(inputs) convs = [] for kernel_size in [2, 3, 4]: c = Conv1D(128, kernel_size, activation='relu')(x) c = GlobalMaxPooling1D()(c) convs.append(c) x = Concatenate()(convs) x = Dropout(0.5)(x) x = Dense(64, activation='relu')(x) outputs = Dense(1, activation='sigmoid')(x) model = Model(inputs, outputs) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) return model model = build_textcnn() model.summary() history = model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=10, batch_size=64)参数说明:MAX_WORDS=20000覆盖大部分常见词,超出部分映射为 OOV;MAX_LEN=100是截断长度,短于 100 补零,长于 100 截断,这个值根据评论长度分布的第 95 分位数来定;EMBED_DIM=128是词向量维度,数据量小可以降到 64,数据量大可以升到 256;卷积核尺寸 2、3、4 分别对应 bigram、trigram、4-gram 特征;Dropout(0.5)防止过拟合。训练 10 轮,如果验证集准确率不再上升,可以提前停止。
3.3 训练过程中的监控与调参
训练时重点看两个指标:验证集准确率和验证集损失。如果训练集准确率持续上升但验证集准确率下降,说明过拟合,解决办法是增大 Dropout、减少模型参数、增加数据量。如果两者都低,说明欠拟合,可以增大 Embedding 维度或增加卷积核数量。
学习率默认用 Adam 的 0.001,如果损失震荡,降到 0.0005。批次大小 64 是平衡内存和收敛速度的常用值,显存不够降到 32。类别不平衡时,在model.fit里加class_weight={0: 1.0, 1: 2.0},具体权重按正负样本比例倒数来设。
4. 推理接口封装与系统集成:让模型真正被调用
4.1 用 Flask 封装一个预测接口
模型训练完保存为textcnn.h5,推理时需要加载模型和 tokenizer。tokenizer 的配置要一起保存,否则新数据的词表映射对不上。
import pickle from flask import Flask, request, jsonify from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.sequence import pad_sequences import jieba app = Flask(__name__) model = load_model('textcnn.h5') with open('tokenizer.pkl', 'rb') as f: tokenizer = pickle.load(f) with open('stopwords.txt', 'r', encoding='utf-8') as f: stopwords = set([line.strip() for line in f]) def preprocess(text): words = jieba.lcut(text) words = [w for w in words if w not in stopwords and len(w) > 1] seq = tokenizer.texts_to_sequences([' '.join(words)]) return pad_sequences(seq, maxlen=100, padding='post', truncating='post') @app.route('/predict', methods=['POST']) def predict(): data = request.get_json() text = data.get('text', '') if not text: return jsonify({'error': 'empty text'}), 400 X = preprocess(text) prob = float(model.predict(X)[0][0]) label = 'positive' if prob > 0.5 else 'negative' return jsonify({'label': label, 'confidence': round(prob, 4)}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)接口接收 JSON 格式的{"text": "评论内容"},返回标签和置信度。prob > 0.5是二分类阈值,如果业务上更怕漏判负面评论,可以把阈值降到 0.4,让更多评论被判为负面。host='0.0.0.0'允许外部访问,生产环境建议用 gunicorn 启动多进程。
4.2 批量分析与结果落库
单条预测适合实时接口,批量分析适合离线任务。把评论从数据库读出来,逐批预测,结果写回新表。
import sqlite3 import pandas as pd conn = sqlite3.connect('reviews.db') df = pd.read_sql('SELECT id, review FROM comments WHERE analyzed = 0', conn) results = [] for _, row in df.iterrows(): X = preprocess(row['review']) prob = float(model.predict(X, verbose=0)[0][0]) results.append((row['id'], 'positive' if prob > 0.5 else 'negative', prob)) cursor = conn.cursor() cursor.executemany('UPDATE comments SET sentiment=?, confidence=?, analyzed=1 WHERE id=?', [(r[1], r[2], r[0]) for r in results]) conn.commit() conn.close()批量预测时verbose=0关闭进度条,避免日志刷屏。每批建议不超过 1000 条,太多会占内存。更新语句用executemany批量提交,比逐条 update 快一个数量级。
5. 避坑与排查:情感分析系统最常见的五个翻车点
5.1 否定词被停用词表误杀
现象:模型把“不好看”预测为正面,把“不推荐”预测为正面。原因:停用词表里包含“不”“没”“别”,分词后否定词被去掉,情感极性反转。解决:检查停用词表,把否定词和程度副词全部移除,或者用 jieba 的自定义词典把“不好看”“不推荐”作为整体词加入。
5.2 训练集和推理集预处理不一致
现象:离线评估准确率 90%,上线后接口预测结果乱七八糟。原因:训练时用了停用词过滤和长度过滤,推理时忘了做同样处理,或者 tokenizer 重新 fit 了一遍,词表索引对不上。解决:把预处理逻辑封装成同一个函数,训练和推理都调用它;tokenizer 必须保存并在推理时加载,不能重新 fit。
5.3 置信度阈值设错导致业务误判
现象:大量中性评论被强行判为正面或负面,运营看到结果觉得不准。原因:二分类模型没有“中性”类别,所有输入都会被分到一边。解决:设置置信度区间,比如 prob 在 0.4 到 0.6 之间标记为“中性/待人工复核”,只对两端结果自动采纳。阈值根据业务容忍度调整,宁可多转人工,不要错判。
5.4 过拟合导致验证集指标虚高
现象:训练集准确率 99%,验证集只有 82%,测试集更低。原因:模型参数太多、数据太少、训练轮数太多。解决:减小 Embedding 维度和卷积核数量,增大 Dropout 到 0.5 以上,加 L2 正则化,或者用早停(EarlyStopping)在验证集损失不再下降时停止训练。
5.5 中文编码问题导致读取乱码
现象:CSV 读取时报 UnicodeDecodeError,或者中文显示为乱码。原因:文件编码不是 UTF-8,可能是 GBK 或 GB2312。解决:读取时指定encoding='utf-8',如果报错就试encoding='gbk';保存时统一用encoding='utf-8-sig',这样 Excel 打开也不会乱码。
6. 进阶技巧:用多模态思路提升影视评论分析的上限
纯文本情感分析在电影评论场景有一个天然短板:很多评论配了截图、表情包或评分星级,这些信息文本里没有。如果你想把系统做得更准,可以往多模态方向走一步。常见做法是:文本走 TextCNN 或 BERT 分支,星级评分走一个数值特征分支,两个分支的输出拼接后再过一个全连接层做最终分类。星级特征可以直接用,也可以归一化到 0 到 1 之间。
另一个实用技巧是领域自适应。公开数据集训练的模型直接用在电影评论上,准确率通常会掉 5 到 10 个百分点,因为电影评论有大量专有名词和圈内表达。解决办法是用少量标注过的电影评论对模型做微调,哪怕只有 500 条,也能明显提升。微调时学习率调小到 1e-4,只训练最后几层,避免破坏预训练特征。
验证方法上,不要只看准确率。情感分析更该看 F1 分数和混淆矩阵,尤其是负面类别的召回率。如果业务上更怕漏掉负面评论,就盯着负面召回率调阈值。我一般会在测试集上跑一遍,把预测错误的样本导出来人工看 50 条,往往能发现数据标注问题或预处理遗漏,比调参管用得多。
最后说一个习惯:每次改完预处理或模型结构,先在小样本(比如 1000 条)上跑一遍,确认流程通了再上全量。我吃过亏,全量训练跑了两个小时,结果发现分词函数里有个 bug,白跑。希望帮到你。
本文还有配套的精品资源,点击获取