news 2026/10/28 23:09:18

中文情感分析实战:基于电影评论的MLP、CNN与LSTM模型实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
中文情感分析实战:基于电影评论的MLP、CNN与LSTM模型实现

简介:基于中文电影评论的情感分析项目资源,面向自然语言处理初学者与研究者,围绕MLP、CNN与LSTM三种主流神经网络模型,实现对中文影评的情感极性自动判别,并系统对比不同架构在短文本分类任务上的表现。压缩包共包含18个文件,大小约6.15MB:3个Python脚本分别对应预处理、模型实现与运行流程,3个txt文件提供训练集和验证集,6张png呈现各模型的准确率曲线与混淆矩阵等对比图表,另有README及配置文件说明环境依赖与使用方式。已有67人浏览学习。从资源中可直接获得完整的可运行代码、已划分的训练与验证数据、分词与去停用词等预处理步骤,以及三组模型训练过程中的历史指标记录。读者既能复现实验,也能进一步调整嵌入维度、卷积核、隐藏层等超参数,透过可视化结果理解MLP、CNN与LSTM在捕捉中文文本局部特征与长距离依赖时的差异,为后续情感分析任务提供可扩展的调优思路与代码基础。

1. 中文情感分析:这份电影评论数据集与 NN(MLP)、CNN、LSTM 三类模型实现到底能拿来做什么

做 NLP 的人应该都有同感,情感分析是最适合练手的任务:数据好找、标注清晰、效果可感知。但如果只在网上下个现成的英文 IMDb 情感分析跑一遍,换到中文场景就会发现处处卡壳——中文分词、停用词、字 vs 词、词向量怎么对齐,全是问题。这份基于中文电影评论的情感分析资源,核心就干一件事:把「中文数据清洗 → 文本向量化 → MLP/CNN/LSTM 三类模型训练与对比」这条完整链路一次打通。它适合两种人——刚入门深度学习、想在 NLP 上做第一个完整项目的初学者,以及已经在做文本分类、想快速拿到一份可直接跑通的中文情感基线代码的老手。资源里既有标注好的中文电影评论数据,也有三类模型的可复现代码,解决了中文场景下「有数据没代码、有代码跑不通」的常见痛点。

2. 先把地基打牢:中文评论数据预处理与文本向量化的四个关键环节

2.1 原始数据长什么样,清洗时要处理哪些脏数据

中文电影评论数据集常见的原始格式是 CSV 或 JSON,每条样本包含评论文本和一个情感标签(通常 0 表示负面,1 表示正面,也有数据用 2 分类的「差评/好评」)。下载这份资源后,先用 pandas 载入看一眼分布:

import pandas as pd df = pd.read_csv('movie_reviews.csv', encoding='utf-8') print(df.head()) print(df['label'].value_counts()) print(df.isnull().sum())

逻辑说明:head()用于确认字段名和文本格式,value_counts()看标签是否均衡——如果正负样本差得太多,后面对模型效果评估会有影响,isnull()则是查空值。我见过不少人在这一步直接跳过,结果后续train_test_split报错或者模型 loss 跳成 NaN,其实源头就是空文本。

再往下就是清洗。中文评论文本里最常见的噪声有三类:HTML 标签(抓取时留下的)、表情符号、以及大量无意义的重复标点。处理方式一般是这样:

import re def clean_text(text): if not isinstance(text, str): return '' text = re.sub(r'<[^>]+>', '', text) # 去 HTML 标签 text = re.sub(r'[0-9a-zA-Z]+', '', text) # 去英文和数字 text = re.sub(r'[\u4e00-\u9fa5]', ' ', text) # 只保留中文 return text.strip()

参数说明:第二个正则删掉英文单词和数字是因为电影评论里夹杂的英文多数是片名或无关信息;第三个正则的意图是反过来剔除标点、表情,只留中文。实际用的时候要注意,第三行正则写的是「把连续中文字符替换成空格」,不是「只保留中文」——想要后者应该用re.sub(r'[^\u4e00-\u9fa5]', '', text)。这种细节最容易翻车,建议动手改代码前先想清楚要的效果是对齐还是剔除。

另外,评论里常见的「哈哈哈哈哈哈哈哈」「好看好看好看好看」这类重复词,清洗时如果不做长度去重,会在后面向量化时产生大量无意义的重复 n-gram 特征。我一般会加一个简单的连续重复压缩:re.sub(r'(.)\1{3,}', r'\1', text),把连续重复 3 次以上的字符压缩成一个。

2.2 分词工具怎么选:jieba 的两种模式与停用词表的作用

中文文本向量化前必须先分词,jieba 是事实标准。资源里大概率已经内置了分词代码,但理解它在做什么比能跑更重要。jieba 有两种常用模式:精确模式和搜索引擎模式。情感分析用精确模式就够,搜索引擎模式把长词再拆出子词,反而引入噪声。

import jieba def tokenize(text, stopwords): words = jieba.lcut(text) # 精确模式分词 words = [w for w in words if w not in stopwords] return words

参数说明:jieba.lcut返回的是 list,比jieba.cut(返回生成器)更直观,适合后面做向量化。停用词表建议直接用哈工大停用词表扩充版,资源里通常会附一份。要注意的是,情感分析场景下「不」「没」「太」这类否定词不能被当停用词删掉——「不好看」删掉「不」就变成「好看」了,语义完全反转。很多第一次做的人在这步踩坑,准确率上不去的其中原因就出在这里。所以停用词表选好后,一定要手动检查一遍是否包含否定词和程度副词,有的话必须移除。

2.3 向量化的两条路线:词级 TF-IDF 与字/词级序列编码

文本向量化是决定模型上限的环节。这份资源里,MLP 用的是词级 TF-IDF 向量,CNN 和 LSTM 用的是序列化的词索引。两条路线要分清,因为它们的输入维度完全不一样。

TF-IDF 的做法是构建词表,然后给每篇评论生成一个固定长度的稀疏向量:

from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer(max_features=8000) X_tfidf = vectorizer.fit_transform([' '.join(tokens) for tokens in all_tokens])

参数说明:max_features=8000是只保留出现频率最高的 8000 个词,目的是控制维度,防止稀疏矩阵过大拖慢 MLP 训练。如果机器内存小,可以压到 5000,但不要低于 3000,否则信息损失太明显。「注意」:TF-IDF 的语料统计一定要在训练集上做 fit,然后 transform 验证集和测试集,不能在整个数据集上 fit——否则验证集的词分布信息提前泄漏给模型,评估结果会虚高。

序列化编码的典型做法是:先按出现频率给每个词编一个从 1 开始的 ID(0 保留给 padding),然后把每条评论转成等长的 ID 序列:

from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences tokenizer = Tokenizer(num_words=20000) tokenizer.fit_on_texts([' '.join(tokens) for tokens in all_tokens]) sequences = tokenizer.texts_to_sequences([' '.join(tokens) for tokens in all_tokens]) X_padded = pad_sequences(sequences, maxlen=120, padding='post', truncating='post')

参数说明:num_words=20000是词表上限,maxlen=120是每条评论固定的序列长度——评论超过 120 个词截断,不足的用 0 补齐。padding='post'是尾部补零,truncating='post'是尾部截断。这两个参数对 LSTM 的影响非常大,尾部截断会直接丢掉评论的后半段内容,如果你觉得电影评论的结尾往往是「总结性评价」,那truncating='pre'(保留尾部)可能更合理。这类参数没有绝对标准,我的习惯是先在验证集上比较两种方案。

2.4 训练集划分与标签编码

数据划分要保证三点:标签分布一致、不混入重复样本、验证集至少 1000 条。

from sklearn.model_selection import train_test_split X_train, X_val, X_test, y_train, y_val, y_test = train_test_split( X_padded, y, test_size=0.2, stratify=y, random_state=42 )

逻辑说明:stratify=y保证切分后正负样本比例与原数据集一致,random_state=42固定随机种子,让实验可复现。这里有一点容易被忽略——评论区里偶尔有完全相同的复制评论,如果不先去重就直接划分,同一条文本可能同时出现在训练集和测试集,模型评估的准确率会虚高几个点。稳妥做法是在清洗后先做df.drop_duplicates(subset=['text'])。

3. 用 MLP 跑通基线:全连接网络做情感分析的原理、结构与调参

3.1 为什么先用全连接网络而不是直接上深度模型

很多入门者一上来就试 LSTM,结果训练慢、调参难、效果还不见得比简单模型好。MLP 在情感分析里的价值是建立基线——它告诉你「在不考虑词序信息的前提下,模型能到多少准确率」。如果一个 MLP 能到 82%,那 CNN/LSTM 做不到 85% 以上就是有问题的,趁早检查数据或者模型实现。

全连接网络在文本上的思路其实是把每条评论的向量当成一个高维点,让模型学一个分类面。它的局限是很明显的——词与词的相对位置信息在展开成向量时全部丢失,「很不好」和「不好很」在词袋向量里几乎是同一个表示。所以 MLP 的上限是被信息表示卡住的,它跟数据量关系不大。

3.2 MLP 的模型架构与关键参数说明

用 TF-IDF 向量做输入的 MLP 结构通常长这样:

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout model = Sequential() model.add(Dense(256, activation='relu', input_shape=(X_tfidf.shape[1],))) model.add(Dropout(0.5)) model.add(Dense(128, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(1, activation='sigmoid')) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

逻辑说明:第一层和第二层的relu激活函数用来让网络具备非线性表达能力,Dropout(0.5)是对抗过拟合——全连接层参数量巨大,不做 dropout 的话训练集准确率很容易冲上 98%,验证集只有 80%。最后一层sigmoid输出一个 0 到 1 的打分,大于 0.5 判为正面。

参数说明:input_shape的维度取决于 TF-IDF 的max_features,改前面那个值后这里必须同步改。隐藏层 256/128 是常见的中等规模配置,如果你的机器是 CPU 跑,可以降到 128/64,速度提升但精度可能略降。优化器用 adam 就行,学习率用默认的 0.001 在多数场景下已经够稳。

3.3 训练策略与过拟合的判别标准

训练 MLP 时关注两个曲线:训练准确率和验证准确率。两者差距超过 5 个百分点就是过拟合信号,优先调大Dropout而不是加数据——因为清洗后的电影评论数据就那么多,加数据通常不现实。

history = model.fit( X_tfidf_train, y_train, validation_data=(X_tfidf_val, y_val), batch_size=64, epochs=20, verbose=1 )

参数说明:batch_size=64是每轮梯度更新用的样本数,内存小就降到 32。epochs=20是训练轮数,我习惯设大一点然后用 EarlyStopping 在验证集不再提升时提前停,既省时间又能避免后面几个 epoch 纯粹在拟合噪声。

from tensorflow.keras.callbacks import EarlyStopping early_stop = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True) model.fit(..., callbacks=[early_stop])

逻辑说明:patience=3表示连续 3 轮验证集 loss 没有下降就停止训练,restore_best_weights=True会把模型权重回退到验证集最优的那一轮。这个机制对 MLP、CNN、LSTM 三套模型通用,建议直接写进公共代码模块里。

4. CNN 与 LSTM 的中文文本分类复现:核心区别、实现要点与超参数对照

4.1 为什么 CNN 能抓局部特征,LSTM 能抓序列依赖

MLP 丢掉了词序信息,而电影评论里情感表达高度依赖局部短语:「演技炸裂」「剧情拖沓」「全程无尿点」——这些 2 到 4 个词的组合是情感判断的最强信号。CNN 在文本上做的就是用一个固定宽度的卷积核滑过词序列,每个窗口提取一组局部 n-gram 特征。举个例子,窗口大小为 3 的卷积核看到的是连续三个词组成的局部片段,多核并行就等于同时关注多种长度的短语组合。

LSTM 走的是另一条路:按照词出现的先后顺序逐个处理,每个时间步的隐状态携带之前所有词的累计信息。它天然适合捕捉「前面的转折词如何影响后面的评价」——比如「虽然前半段很精彩,但结局莫名其妙」这类带转折的句子,LSTM 有机会学到前半段的正面信号被后半段的负面信号覆盖。这也是 LSTM 在情感分析上通常略强于 CNN 的原因。

4.2 文本 CNN 的搭建:卷积核宽度、数量与池化方式

文本 CNN 是 Kim 在 2014 年提出的经典结构,资源里的实现基本遵循这个框架:

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, Conv1D, GlobalMaxPooling1D, Dense, Dropout model = Sequential() model.add(Embedding(input_dim=20000, output_dim=128, input_length=120)) model.add(Conv1D(filters=128, kernel_size=3, activation='relu')) model.add(GlobalMaxPooling1D()) model.add(Dense(64, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(1, activation='sigmoid')) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

逻辑说明:Embedding层把每个词的 ID 映射成一个 128 维的稠密向量,Conv1D在词向量序列上做卷积——这里的kernel_size=3是每次覆盖 3 个词,filters=128表示同时学习 128 种不同的局部模式。GlobalMaxPooling1D在每个特征图上取最大值,相当于从整条评论里提取每个卷积核最激发的那个位置的特征。

参数说明:input_dim=20000必须和上一章 Tokenizer 的num_words一致,input_length必须和pad_sequences的maxlen一致。想提升效果,最常见的做法是并排多个不同kernel_size的卷积核,比如 2/3/4 各来 128 个——资源里如果只给了单卷积核版本,可以自己改造,这属于性价比最高的进阶改动。

4.3 LSTM 的搭建:嵌入层、循环层与过拟合控制

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout model = Sequential() model.add(Embedding(input_dim=20000, output_dim=128, input_length=120, mask_zero=True)) model.add(LSTM(units=128, dropout=0.3, recurrent_dropout=0.3)) model.add(Dense(1, activation='sigmoid')) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

逻辑说明:mask_zero=True是关键配置——告诉 LSTM 跳过 padding 出来的 0 位置,不做无效计算。units=128是隐状态维度,越大模型容量越高但越容易过拟合。dropout=0.3作用于输入到隐层的连接,recurrent_dropout=0.3作用于隐层之间的循环连接,两者同时开是 LSTM 防过拟合的标准做法。

参数说明:LSTM 的训练速度比 CNN 慢得多,在同样的数据量下建议先把units从 128 降到 64 跑通流程,再逐步加大。另外一个很实际的建议——如果评论平均长度已经接近 120,LSTM 处理长序列的负担会比较大。可以考虑用双向 LSTM(Bidirectional 包裹一层)来增强效果,代价是参数量和训练时间翻倍。初学阶段先跑通单向版本,记录效果,再决定要不要加双向。

4.4 三类模型的效果对比表与选型结论

模型关键特点训练速度预期准确率区间适用场景
MLP忽略词序,全连接拟合最快78% - 82%基线/快速验证数据质量
CNN捕捉局部 n-gram 特征较快82% - 86%短评/短语为主的情感表达
LSTM建模序列依赖,捕捉转折语义较慢83% - 88%长评/含转折结构的表达

表格里的准确率区间是这类中文数据集的常见水平,不代表资源里能直接跑出这个值——你的分词质量、清洗强度、序列长度设置都会影响最终数字。选型结论其实很直接:如果做线上实时推理,CNN 更快更稳;如果追求极致准确率且推理时延不敏感,LSTM 更合适;MLP 不是拿来上线的,是拿来做数据 sanity check 的。

5. 避坑排查:训练与评估阶段最常见的六个翻车现场

5.1 训练集准确率极高但测试集一塌糊涂

现象:训练三个 epoch 后训练集准确率到 95%,测试集只有 70%,而且还在下降。

原因:这是典型的过拟合叠加信息泄漏。信息泄漏部分多出在 TF-IDF 的fit步骤用到了全部数据;过拟合部分则是模型容量太大(隐藏层神经元太多)而数据量不足。

解决:先把TfidfVectorizer改成先在训练集上做fit,再transform验证集;然后把Dropout从 0.3 提到 0.5,同时把隐藏层神经元减半。

5.2 LSTM 训练 loss 永远是 NaN

现象:训练第一个 epoch loss 就成了 NaN,之后一直不变。

原因:多数是嵌入层没有做合理的参数初始化,或者学习率太大导致梯度爆炸。也有人把未清洗的文本直接送进 Tokenizer,产生了异常的 token 序列。

解决:确认输入序列中所有值都是非负整数索引,且小于num_words;把优化器换成Adam并把clipnorm设为 1.0,限制梯度范数。常见做法是Adam(learning_rate=0.0005, clipnorm=1.0),对 LSTM 这类循环网络非常有效。

5.3 分词后的文本为空或者长度全部为 1

现象:pad_sequences之前打印每条序列的长度,发现大量为 1 或 0。

原因:清洗时把非中文的字符全部删掉后,有些评论原本就以英文为主或者标签符号居多,剩下的就是空文本。分词后这些空文本的 token 列表为空,Tokenizer 会把它编成空序列。

解决:清洗后加一步过滤,凡len(tokens) < 3的样本直接丢弃。同时检查停用词表是否误删了「不」「别」这类词——如果删了,很多格式为「不推荐」「别浪费时间」的评论分词后只剩「推荐」「浪费」,长度确实保存了但语义完全反转。

5.4 验证集准确率波动幅度极大

现象:验证集准确率不是稳定上升,而是在 70% 到 85% 之间疯狂横跳,训练集倒是很平滑。

原因:验证集样本量太小。如果总数据只有两三千条,train_test_split切出 20% 验证集不足千条,评估方差很大,每个 epoch 抽到的验证批次不同导致分数剧烈波动。

解决:先用stratify=y保证标签分布一致,然后把验证集比例从 0.2 提到 0.3,或者干脆用 5 折交叉验证评估最终效果。训练过程仍然用单一验证集做 early stopping,最后再交叉验证一次取均值。

5.5 预测结果一直是同一个标签

现象:模型能正常训练,准确率也还行,但用model.predict()对新评论推理时全部输出 0.5 附近,或者全部输出同一个标签。

原因:最大可能是预测时用的向量化器和训练时不一致——比如训练用 TF-IDF,预测时重新定义了一个没fit过的向量化器,特征空间根本对不上。另一种可能是推理时文本没有走清洗流程,评论文本里的一个字被映射成全零向量。

解决:把向量化器用pickle.dump保存下来,和模型权重放在同一个目录,预测前原样加载。这步我在实际项目中翻过车——训练时写了个preprocess()函数,预测时以为它会把原始文本自动处理,结果函数内部依赖的停用词表路径写的是相对路径,部署环境一换就找不到文件。从那以后我每次做完实验,都会强制把 tokenizer 和清洗函数一起导出,再写一个单独的 inference 脚本,用真实清洗后的文本走一遍模型预测,确认输出分布的合理性,再算实验收工。希望这份资源的复现过程也能帮你少走几次这些弯路,把时间花在调模型而不是调 bug 上。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 23:07:37

AI Agent Skills 深度拆解:从提示词工程到确定性任务执行的进化

我最早接触 Skills 这个词&#xff0c;是因为项目的 issue 区里有人吐槽&#xff1a;"把提示词封装一下就叫 Skill&#xff0c;这跟之前的 Prompt 工程有什么本质区别&#xff1f;"当时我也抱着同样的怀疑&#xff0c;直到自己动手把一个高频重复的数据处理流程真正做…

作者头像 李华
网站建设 2026/10/28 23:07:01

Prometheus容器化监控实战:Compose搭建、配置与排错

1. 容器化 Prometheus 到底解决了什么问题前段时间帮一个团队收拾监控烂摊子&#xff0c;宿主机上那份 Prometheus 是两年前手工装的&#xff0c;配置文件散落在三个目录&#xff0c;版本号只记在某位同事的聊天记录里&#xff0c;机器一换就得从头再来一遍。这类场景我见过太多…

作者头像 李华
网站建设 2026/10/28 23:06:17

汽车网络安全标准融合:CC、FIPS与PCI的实践应用

1. 汽车网络安全标准的现状与挑战2026年第九届AutoCS汽车网络安全峰会上&#xff0c;atsec作为国际领先的安全评估机构&#xff0c;分享了将CC&#xff08;Common Criteria&#xff09;、FIPS&#xff08;Federal Information Processing Standards&#xff09;和PCI&#xff0…

作者头像 李华
网站建设 2026/10/28 23:03:19

TypeScript元组转对象:类型体操实战指南

1. 元组与对象的基础概念解析在TypeScript的类型系统中&#xff0c;元组&#xff08;Tuple&#xff09;和对象&#xff08;Object&#xff09;是两种截然不同但又经常需要互相转换的数据结构。让我们先明确它们的本质区别&#xff1a;元组是固定长度、固定类型顺序的数组类型。…

作者头像 李华
网站建设 2026/10/28 23:03:18

SDUP 2026:可持续发展与城市规划国际学术会议前瞻

1. 会议背景与核心价值2026年可持续发展与城市规划国际学术会议&#xff08;Sustainable Development and Urban Planning International Conference 2026&#xff0c;简称SDUP 2026&#xff09;是一场聚焦未来城市发展路径的全球性学术盛会。在当前全球城市化进程加速的背景下…

作者头像 李华
网站建设 2026/10/28 23:03:13

领域驱动设计示例代码拆解:四层架构与聚合实现指南

简介&#xff1a;这份资源是领域驱动设计&#xff08;DDD&#xff09;的官方示例代码&#xff0c;面向希望将 DDD 方法论落地到真实业务的中高级开发者与架构学习者。它以船运系统为背景&#xff0c;把领域模型、聚合、实体与值对象、领域事件、领域服务、限界上下文、仓储持久…

作者头像 李华