news 2026/10/9 3:50:29

中文情感分析毕设:CNN与BI-LSTM双模型文本分类项目实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
中文情感分析毕设:CNN与BI-LSTM双模型文本分类项目实践

简介:基于Python的中文情感分析项目,涵盖卷积神经网络与双向长短期记忆网络,提供完整的文本分类解决方案。项目定位清晰,面向计算机相关专业毕业生、期末大作业及课程设计学生,也适合对自然语言处理感兴趣的开发者阅读参考。资源包共三十五份文件,压缩包大小约七十三点二兆,核心为十三个Python脚本,分别实现语料读取、数据预处理、模型定义、训练验证与评价指标输出,代码内部注释详细,降低上手门槛;模型部分包含训练好的pb权重文件及数据分片,可直接加载推理,另有文本数据、说明文档、示例图像等辅助配置,整体目录结构直观。项目经过严格调试,部署流程简单,在毕业设计中获得九十八分评价,具备较高的实际参考价值。目前已有七十四人浏览/学习,适合作为可运行的高分项目模板或中文情感分析实战样例,直接使用或二次开发均很方便。

1. 中文情感分析毕设:CNN 与 BI-LSTM 双模型文本分类,值不值得拿来改

中文情感分析在毕业设计里几乎是个常驻选题,核心任务就是给定一段中文文本,判断它是好评还是差评,本质上是一个二分类的文本分类问题。这个项目把 CNN 和 BI-LSTM 两条技术路线放在同一个工程里,数据、模型、训练、评分报告脚本都齐了,代码里还带了注释,属于那种解压之后改改路径就能跑的类型。Python 技术栈,适合做毕业设计、期末大作业或者课程设计。对新手来说,它是一份能跑通的深度学习入门底子;对时间紧的熟手来说,模型对比实验和报告输出部分可以直接抄作业。

2. 拆解项目结构:data、model、score_report.py 各管哪一段

2.1 目录职责与运行链路

先把项目根目录下的文件职责理清楚。第一次拿到这类压缩包,最忌讳的是上来就找 train.py 双击运行,先花五分钟看清楚每个目录是干什么的,后面改参数时才不会改错地方。

路径职责
data/存放原始数据与切分后的训练、测试中间文件
hotel_comment/酒店评论原始语料,CSV 或文本格式
model/训练完成后保存的模型权重文件
cnn/CNN 模型定义与训练脚本
lstm/BI-LSTM 模型定义与训练脚本
score_report.py评测脚本,输出分类报告与指标
README.md项目说明,包含运行步骤与依赖清单

这条链路是固定的:数据读入 → 预处理(分词、过滤、标签化)→ 词表构建 → 模型训练(CNN 或 BI-LSTM)→ 评估 → score_report.py 生成报告。对应到代码上就是三个入口,数据准备入口在 data 目录,模型入口在 cnn 和 lstm 目录,报告入口是 score_report.py。

我之前接手类似项目时习惯先顺着这条链路把文件过一遍,确认数据源路径和输出位置,再决定自己是全量跑还是只跑其中一段。这个项目的好处是模块边界清楚,预处理、训练、评估各自独立,你可以只替换数据预处理部分,也可以只改模型结构,不影响其他环节。

2.2 环境准备:Python 版本与依赖安装

这类项目一般依赖 jieba、numpy、pandas 和深度学习框架。先确认 Python 版本,再按依赖安装。常见做法是直接建一个虚拟环境,避免和系统里的其他项目互相污染,尤其是 TensorFlow 这种重依赖,版本冲突起来非常头疼。

python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install jieba numpy pandas scikit-learn pip install tensorflow

第一行命令创建虚拟环境,第二行激活,第三行装的是数据预处理和评估用的库,第四行装深度学习框架。TensorFlow 2.x 分支下 Keras 是内置的,直接 import 就能用,不需要额外装 keras 这个包。

这里有个细节:scikit-learn 不是训练必需的,但 score_report.py 里通常用 sklearn 的 classification_report 和 confusion_matrix 来生成指标,所以建议装。numpy 和 pandas 负责数据读取和向量化,jieba 是中文分词的核心依赖,漏掉任何一个都会在预处理阶段直接报 ImportError。我一般会在装完后先跑一下 import 检查,五个库全部通过再往下走,省得后面反复找问题。

2.3 第一次启动:先跑哪条命令

很多人第一次启动就卡住,不是因为代码有问题,而是不知道入口在哪。建议先打开 README.md,看它写的运行顺序。这个项目的典型启动顺序是:先跑数据预处理脚本,再跑 cnn 或 lstm 的训练脚本,最后用 score_report.py 对训练好的模型做评估。

python data/preprocess.py python cnn/train.py python score_report.py --model cnn

第一条命令把酒店评论原始数据切分成训练集和测试集,并保存成中间文件;第二条命令训练 CNN 模型,权重保存到 model/ 目录;第三条命令加载 model/ 下的权重,对测试集做预测并输出准确率、F1 和混淆矩阵。如果你只想先验证环境通不通,建议先跑 preprocess.py,它耗时最短,而且能暴露大部分依赖问题。

3. 中文文本分类的预处理:分词、标签映射与 embedding 构建

3.1 中文分词:jieba 精确模式与停用词过滤

中文情感分析绕不开分词。英文按空格切就行,中文没有天然分隔符,得靠 jieba 这类工具先切成词,模型才能读懂。jieba 有三种模式,精确模式是最常用的,它把句子切成最合理的词序列,适合做分类输入。全模式会把所有可能的分词结果都列出来,词数膨胀,反而干扰模型。搜索引擎模式适合检索场景,这里用不上。

import jieba def tokenize(text): words = jieba.lcut(text.strip()) # 精确模式分词 words = [w for w in words if w.strip()] # 去掉空白项 return words sample = "这个酒店位置不错,但卫生条件一般" print(tokenize(sample)) # ['这个', '酒店', '位置', '不错', ',', '但', '卫生', '条件', '一般']

jieba.lcut 返回的是分词后的列表,比 jieba.cut 返回的生成器更直观,方便后面直接传给向量化函数。第二步的列表推导式是把空字符串滤掉,因为连续标点或特殊符号会产生空项,不滤会让后面的词表统计出一些奇怪的 key。

这里有个血泪经验:标点符号要不要保留,直接决定模型表现。很多人直接把逗号句号删掉,结果“不错,一般”这种转折句被拼成“不错一般”,语义完全变了。我一般保留全部标点,让模型自己学标点的权重,效果反而更稳。停用词表可以过滤“的”“了”“吗”这类高频虚词,但不要过滤标点。

3.2 标签映射与数据集切分

情感分析的标签通常是好评或差评,对模型来说是整数。映射逻辑很简单,好评映射成 1,差评映射成 0。关键在数据集切分,原始酒店评论数据需要按比例切成训练集、验证集和测试集,三个集合互不重叠,否则评估结果就是虚高的。

import pandas as pd from sklearn.model_selection import train_test_split df = pd.read_csv("data/hotel_comment.csv") df["label"] = df["sentiment"].map({"好评": 1, "差评": 0}) train_df, test_df = train_test_split( df, test_size=0.2, random_state=42, stratify=df["label"] ) print(train_df.shape, test_df.shape)

map 函数把文本标签转成数值,stratify=df["label"] 保证切分后训练集和测试集里好评差评的比例和原始数据一致,这个参数在正负样本不平衡时特别重要。random_state 设成固定值,保证每次运行切出的数据一样,不然你训练到一半发现结果和昨天不一样,会以为是模型玄学,其实是切分变了。

切分完成之后,建议顺手打印一下两个集合的标签分布。如果训练集里差评只占 10%,测试集里差评占 30%,那模型学到的分布和验证分布不一致,准确率指标基本没有参考价值。

3.3 词表构建与 embedding 初始化

分词之后要建词表,把每个词映射成一个整数 id,再把句子 pad 成等长。深度学习模型的输入必须是定长张量,所以短句补零、长句截断。max_len 是这条链路里第一个需要拍脑袋的参数,对酒店评论这种短文本,常见做法是取 50 到 100,太长会引入大量 padding 噪音,太短又会截断关键信息。

from collections import Counter max_len = 80 counter = Counter() for words in train_df["words"]: counter.update(words) word2id = {w: i + 2 for i, (w, _) in enumerate(counter.most_common(19998))} word2id["<PAD>"] = 0 word2id["<UNK>"] = 1 def encode(words): ids = [word2id.get(w, word2id["<UNK>"]) for w in words] return ids[:max_len] + [word2id["<PAD>"]] * (max_len - len(ids))

counter.most_common(19998) 取词频最高的两万个词,加上 PAD 和 UNK 之后词表正好是两万。UNK 用来兜底,测试集里出现的生词统一映射为 1,避免越界报错。encode 函数先截断后补齐,保证每个样本都是 80 长度的向量。

每一条训练数据都要经过 encode 变成整数序列,再转换成 numpy 数组才能喂给模型。这一步最容易踩的坑是 embedding 的维度。如果项目里有预训练词向量,embedding 矩阵要用预训练向量初始化;如果没有,就随机初始化,让模型在训练中自己学。随机初始化不是错误,只是收敛会慢一些。我在这个项目里通常先用随机初始化跑通全流程,确认指标正常后再考虑换预训练向量。

提示:词表文件在项目里的地位和模型权重一样重要,训练完第一件事是把 word2id 落盘成 json,而不是等到换机器时再后悔。

4. 模型实现:CNN 与 BI-LSTM 的结构差异和参数取舍

4.1 CNN 文本分类:卷积核尺寸与池化策略

CNN 在文本分类里的原理是 n-gram 特征提取。卷积核在词向量序列上滑动,一个核尺寸为 3 的卷积相当于抓取连续三个词构成的局部模式。这就是为什么文本 CNN 的卷积核一般设成 3、4、5,而不是图像领域常用的 5x5 或 3x3——句子里的关键模式往往就藏在两三个词的组合里。

from tensorflow.keras import layers, models def build_cnn(vocab_size, embed_dim=128, max_len=80): inputs = layers.Input(shape=(max_len,)) x = layers.Embedding(vocab_size, embed_dim)(inputs) convs = [] for kernel_size in (3, 4, 5): conv = layers.Conv1D(filters=256, kernel_size=kernel_size, activation="relu")(x) pool = layers.GlobalMaxPooling1D()(conv) convs.append(pool) x = layers.Concatenate()(convs) x = layers.Dropout(0.5)(x) outputs = layers.Dense(1, activation="sigmoid")(x) return models.Model(inputs, outputs)

三个卷积核尺寸各跑一遍,各自池化后拼接,相当于同时从不同长度的 n-gram 里提特征。filters=256 是卷积核数量,数量越多能学到的模式越多,但训练也越慢,对于酒店评论这种小规模数据,256 是够用的。GlobalMaxPooling1D 的作用是每个特征图只保留最强烈的那个信号,降低参数量的同时保留关键信息。Dropout(0.5) 是防过拟合的关键,不加的话测试集准确率通常比训练集低五到八个点。

这个结构在中文情感分析里表现很稳定,因为评论这种短文本的判别信息往往集中在几个关键词上,比如“太差”“很好”“不值”,CNN 的局部连接特性正好适合抓这类模式。

4.2 BI-LSTM:双向语义与后向信息

LSTM 擅长捕捉长距离依赖,但单向 LSTM 只能从前往后读,看不到后面的信息。评论里“虽然服务一般,但是环境很好”这种转折句,前面的“一般”要结合后面的“很好”才能判断整体是好评。BI-LSTM 的做法是把句子正着读一遍、反着读一遍,两个方向的隐藏状态拼接,这样每个位置的输出都同时包含上下文信息。

from tensorflow.keras import layers, models def build_bilstm(vocab_size, embed_dim=128, max_len=80): inputs = layers.Input(shape=(max_len,)) x = layers.Embedding(vocab_size, embed_dim)(inputs) x = layers.Bidirectional( layers.LSTM(units=128, dropout=0.3, return_sequences=False) )(x) x = layers.Dropout(0.5)(x) outputs = layers.Dense(1, activation="sigmoid")(x) return models.Model(inputs, outputs)

units=128 是每个方向的隐藏维度,双向之后实际输出的特征维度是 256,相当于模型容量翻倍。dropout=0.3 针对 LSTM 内部循环做正则化,层外的 Dropout(0.5) 再压一次,两层 dropout 叠加是 LSTM 文本分类的常见做法。return_sequences=False 表示只取最后一个时间步的输出,因为这是句子级分类任务,不是序列标注,不需要每个时间步的输出。

这里要提醒一件事:BI-LSTM 的参数量大约是单层 LSTM 的两倍,训练时间也接近两倍。如果你的机器没有 GPU,CPU 上跑 10 个 epoch 可能要等很久,这是选模型之前就要想清楚的事。CNN 在这个场景下收敛更快,BI-LSTM 在长句和复杂转折句上通常表现更好,属于典型的“用时间换效果”。

4.3 训练参数对比:epoch、batch_size、学习率

两个模型的训练配置基本可以共用一套,但有几个参数需要按模型调整。下面是我在这个项目里实际使用的参数组合:

参数CNNBI-LSTM
embedding 维度128128
卷积核数量256 × 3—
隐藏维度—128(双向后 256)
dropout0.50.3 + 0.5
推荐 epoch10~1515~20
单 epoch 耗时快约 2~3 倍

训练时的 compile 和 fit 配置如下:

model.compile( optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"] ) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), batch_size=64, epochs=12, callbacks=[tf.keras.callbacks.EarlyStopping( monitor="val_loss", patience=2, restore_best_weights=True )] )

binary_crossentropy 是二分类的标准损失函数,adam 优化器的默认学习率 0.001 在这个任务里一般不用改。batch_size=64 是文本分类的常用起点,显存或内存紧张时可以降到 32,效果差别不大。EarlyStopping 监控验证集 loss,连续两个 epoch 不下降就停,并恢复最优权重,这个回调比手动调 epoch 数靠谱得多,能省掉不少试错时间。

5. 避坑指南:中文情感分析项目最常见的五个翻车现场

5.1 准确率卡在 50%:问题大概率不在模型

现象:训练正常,loss 在降,但准确率始终在 50% 上下浮动,跟抛硬币一样。

原因:50% 说明模型学到的全是“好评”或者全是“差评”的分布记忆,本质是标签或数据切分出了问题。最常见的原因是标签映射写反了,好评映射成了 0,差评映射成了 1,模型学得越努力错得越离谱。另一个常见原因是切分时没有 stratify,导致训练集和测试集里类别比例失衡,验证集里集中出现单一类别。

解决:先打印一条训练样本的 words 和 label 人工核对,再检查 train_test_split 的 stratify 参数。我一般会在预处理脚本里加一行断言,保证训练集和测试集都同时包含 0 和 1 两个标签,不满足就直接抛异常,而不是等到训练完才发现。

5.2 loss 不降反升,训练曲线像心电图

现象:前几个 epoch loss 正常下降,之后突然反弹,然后一路走高,验证集准确率也跟着崩。

原因:这是典型的过拟合信号,说明模型的拟合能力超过数据量能提供的约束。酒店评论数据集通常只有几千到几万条,CNN 的卷积核数量、LSTM 的隐藏维度都偏大时,模型开始死记训练样本。

解决:先把 Dropout 提高一档,CNN 从 0.5 提到 0.6,BI-LSTM 的两层 dropout 都往上调。同时把 EarlyStopping 的 patience 设为 2,让它在验证集 loss 连续变差时及时停住并回滚到最优权重。还有一个立竿见影的办法是缩小词表,把 most_common 从 20000 降到 10000,噪声特征少了,过拟合也会减轻。

5.3 BI-LSTM 在 CPU 上训练慢到怀疑人生

现象:一个 epoch 跑二十几分钟,12 个 epoch 要四五个小时,笔记本风扇全程满载。

原因:双向 LSTM 的计算量是单向的两倍,加上 embedding 和隐藏维度都偏大,CPU 上真的扛不住。这不是代码 bug,是算力瓶颈。

解决:先确认有没有 GPU,没有的话把 BI-LSTM 的隐藏维度从 128 降到 64,embedding 降到 64,epoch 减到 8。还想更快就截一部分数据跑通验证,比如只取 5000 条训练样本,确认流程没问题后再上全量。如果最终目标是交作业而不是刷指标,优先选 CNN,文本 CNN 在 CPU 上会快得多。

5.4 训练时好好的,predict 新文本时结果全乱

现象:测试集准确率 90% 以上,但自己写一句“这家酒店太棒了”去预测,结果输出 0。

原因:predict 流程和训练流程的预处理不一致。最常见的是训练脚本里用一种方式分词、过滤、pad,预测时又写了另一段代码,词表对不上,padding 长度也对不上,模型输入分布变了,输出自然乱。

解决:把预处理逻辑抽成一个公共函数,训练和预测统一调用。我在实际项目里把 tokenize、encode、pad 全部放进一个 preprocessing.py,预测时从同一个模块 import,绝不复制粘贴。词表也要和权重一起管理,Keras 的模型保存不会自动带上你的 word2id 字典,它只是模型内部的参数,所以词表必须单独保存一份 json,预测前加载。

5.5 换电脑后 Embedding 加载报形状不匹配

现象:项目在别人电脑上明明跑通了,拷到自己机器上一训练就报维度错误,或者加载权重时 shape 对不上。

原因:大概率是词表变了。原来电脑上数据经过预处理后生成了两万词的词表,新环境里如果重新跑了一遍预处理,词频排序可能因为 Python 版本或 pandas 版本不同而微调,导致词表顺序变化,embedding 矩阵的第一维对不上。

解决:词表应该和模型权重一样被视为产物来管理。训练完把 word2id 保存成 json 文件,下次直接 load,不要重新生成。加载权重时也先打印 model.summary() 核对 embedding 层的输入维度,再 load_weights,否则出了错你都不知道错在第几层。这个坑我当时调了大半天,最后发现是同事用了不同版本的 pandas 重新生成了词表。

6. 30 分钟跑通全流程:从解压到拿到情感分析报告的验证路径

6.1 三步验证法

拿到任何文本分类项目,我都习惯先走一遍最短路径验证环境,再谈调优。这个项目的三步验证法如下:

python data/preprocess.py python cnn/train.py --epochs 2 python score_report.py --model cnn

第一步验证数据和依赖,第二步用 2 个 epoch 验证模型结构能跑通,第三步验证评估脚本和模型加载链路。两步如果都能在一个小时内完成,说明环境没有问题,后面可以放心调参。

6.2 结果怎么看

score_report.py 输出的核心是 precision、recall、f1-score 三类指标,以及每个类别的支持度。二分类情感分析里,我习惯同时看正负两个类别的 F1,而不是只看准确率。数据不平衡时准确率会骗人,比如差评只占 10%,模型全预测好评就有 90% 准确率,但 F1 会直接暴露问题。CNN 和 BI-LSTM 两份报告的 F1 差在三个点以内,属于正常浮动,超过五个点就要回头查预处理是否一致。

我拿到这份资源时的习惯是先用 2 个 epoch 跑通,确认 CNN 和 BI-LSTM 两条链路都正常后再调参数。把随机初始化改成预训练词向量、加 EarlyStopping、词表设成 20000 并单独保存 json,这三步做完,后面所有的实验都有了一个稳定参照系。从那以后我每次接手这类文本分类项目,都强制走一遍“先 2 epoch 验证、再全量训练”的流程,省下的全是调参的冤枉时间。希望帮到你。

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

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

流处理编程实战指南:从核心概念到Flink实操

1. 先把话说清楚&#xff1a;流处理到底在解决什么问题先说一个我经常被问到的问题&#xff1a;我已经会写Spark批处理了&#xff0c;为什么还要学流处理&#xff1f;这个问题背后&#xff0c;其实是很多人的真实困惑。传统的数据处理思路是"攒一批、跑一批"&#xf…

作者头像 李华
网站建设 2026/10/9 3:49:47

在线教育平台智能推荐系统的设计实现与大数据分析

做毕业设计那会儿&#xff0c;我选了“大数据驱动的在线教育平台智能推荐系统的设计与实现&#xff08;案例分析&#xff09;-附源码”这个题目。说实话&#xff0c;当时第一眼看到这个题目&#xff0c;脑袋里全是问号&#xff1a;大数据是不是意味着必须搭一套 Hadoop 集群&am…

作者头像 李华
网站建设 2026/10/9 3:48:41

next-terminal轻量级堡垒机:Go与JavaScript统一SSH/RDP运维入口

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/9 3:48:15

OpenClaw部署全攻略:从本地模型到ROS2仿真的半小时实战

最近圈子里OpenClaw部署的话题热度高得离谱&#xff0c;有人调侃“封神级翻车现场”&#xff0c;天天有人问Windows怎么搭、安卓能不能跑、16G显存能不能本地带起来。我拿自己的项目试了一个遍&#xff0c;前后折腾了3天&#xff0c;才算把一套OpenClaw部署环境彻底跑通&#x…

作者头像 李华
网站建设 2026/10/9 3:47:57

Ubuntu下MySQL小版本升级避坑指南:apt与二进制包方案

在Ubuntu上给MySQL做小版本升级&#xff0c;这件事看着小&#xff0c;翻车的方式却一点都不少。我见过有人直接在跑业务的库上解压新版二进制覆盖旧目录&#xff0c;也有人升级完数据库干脆启动不起来&#xff0c;最后发现是数据目录权限被改了。实际上小版本升级是MySQL日常运…

作者头像 李华