ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TensorFlow 2中文情感分析实战:CNN与LSTM模型对比

TensorFlow 2中文情感分析实战:CNN与LSTM模型对比 简介这套基于深度学习的中文情感分析系统源码源自评审分95分以上的课程大作业面向Python相关专业的学生或从业者可作为期末课程设计、大作业或入门实战参考。压缩包共35个文件大小约73.2MB包含13个Python脚本、10个文本说明、2个pb模型文件、模型训练产生的data数据、项目截图与说明文档等目录按CNN与LSTM两类模型以及酒店评论数据等模块清晰组织。已有363人浏览学习。源码经过严格调试并附评分脚本可直接运行内容覆盖酒店评论数据集的预处理、CNN与LSTM模型的构建训练、情感分类评分与结果报告输出结构完整、注释清楚还带有可视化图片与说明文档适合系统学习中文情感分析从数据到落地的全过程也能在此基础上补充数据、调整网络结构或扩展应用。资源包解压后即可使用也便于在此基础上做二次开发。1. 聊一聊这份中文情感分析系统它内核其实是一套可落地的深度学习流程很多人下载中文情感分析源码第一反应是“又要调参调到头秃”但这份基于 TensorFlow 2 的中文情感分析系统源码实际跑完你会发现真正花时间的不是模型——而是数据清洗、序列化、词向量对齐这一整套前置流水线。项目自带酒店评论数据集内置 CNN 和 LSTM 两套独立的神经网络模型带 score_report.py 评分脚本正确配置环境后可以端到端运行从原始文本到预测结果再到评估报告打通了深度学习做中文情感分类的完整链路。它适合这类人正在做 Python 课程设计、需要交付一个可演示的深度学习项目或者想找一份代码规范、注释清晰的中文 NLP 参考工程。这套系统的评分能上 95 分不是靠某个黑盒模型而是靠训练流程完整、代码结构清晰。我拆解完它的目录接下来把每一块的设计意图、运行方式和踩过的坑都摊开来讲。2. 动手前先看懂工程结构数据流、模型文件和两个核心脚本拿到压缩包解压后先不要急着跑。先看目录里有什么再决定从哪个文件入手。项目根目录下有 data、model 两个文件夹含 cnn 和 lstm 两个子目录另有 hotel_comment 数据集目录、score_report.py 脚本、.gitignore 和 README.md。从工程习惯看这个布局很典型——数据与模型分离、脚本独立、按模型划分子目录对大作业而言结构分已经拿到了。2.1 先读 README 和 score_report.py摸清设计的验收逻辑README.md 是入口它写清楚了依赖版本和训练入口即使写得比较简略也要先过一遍。接着把 score_report.py 打开它负责模型评估会加载训练好的模型权重对验证集做预测输出准确率、F1这类关键指标。提示先跑 score_report.py 而不是训练脚本用已训练好的权重做推理能最快验证环境是否正常避免一上来就陷入训练报错。# score_report.py 核心流程结构示意 from sklearn.metrics import accuracy_score, f1_score, classification_report model load_model(model/lstm/best_model.h5) # 加载已训练模型 pred model.predict(x_val_pad) # 对验证集预测 pred_labels pred.argmax(axis1) # 映射为类别索引 print(classification_report(y_val, pred_labels, target_names[负, 正]))这段代码先是加载模型再预测最后打印分类报告。参数核心就是 classification_report 里的 target_names 要和训练时的标签顺序一致否则输出看不懂。这个脚本的存在说明作者把“如何证明模型有效”这件事前置考虑了这是拿高分的关键。2.2 数据从哪来、怎么喂给模型data 和 hotel_comment 目录的配合hotel_comment 目录放原始评论文本data 目录放预处理后的中间结果。用文件方式分阶段缓存数据跑第二次就不用重新清洗一遍这在调试大作业时是实用设计。原始数据大概率是 CSV 或 Excel带“评论内容”和“打分”两列。预处理要做三件事把打分映射成二分类标签比如 1-3 为负4-5 为正、去重、去掉无效行。你可以直接跑项目现有数据也可以把自己的数据集按同样结构放进去。# 数据加载与标签映射常见做法 import pandas as pd df pd.read_excel(hotel_comment/comment.xlsx) df[label] df[score].map(lambda x: 1 if x 4 else 0) df df.dropna(subset[comment]) df df.drop_duplicates(subset[comment]) print(df[label].value_counts()) # 检查类别均衡度这里的 map 操作把打分转成标签dropna 和 drop_duplicates 是为了保证数据质量。类别的分布你打印出来看一眼如果一边倒训练出来的模型就是摆设。我一般会顺手统计一下正负样本比例相差太大要采样或加权。2.3 文本预处理从中文句子到向量序列中文文本不能直接进深度学习模型需要分词、去停用词、转序列。这份源码采用 jieba 分词是中文 NLP 的工业默认选项。预处理流程是分词 → 去停用词 → 构建词表 → 转索引 → padding 到固定长度每一步都会影响最终效果。# 中文文本转序号序列核心代码可逻辑复用 import jieba import joblib with open(data/stopwords.txt, encodingutf-8) as f: # 停用词表 stopwords set([line.strip() for line in f]) def text_to_seq(text, word2idx, max_len70): cut [w for w in jieba.cut(text) if w not in stopwords and w.strip()] ids [word2idx.get(w, 1) for w in cut] # 1 为未登录词编号 if len(ids) max_len: ids ids[:max_len] else: ids ids [0] * (max_len - len(ids)) return ids这段代码的核心逻辑有两个关键点padding 在前补 0截断在尾部截断这是 TextCNN 类模型的标准做法。未登录词编号设为 1实际很多项目会把索引从 2 开始预留 0 给 padding、1 给 OOV这一点务必和词表构建对齐不然后果是预测错位。3. 从零训练你自己的情感分类模型加载词向量、搭建网络跑通两条模型链前面把数据管线梳理完现在进入核心训练。这份源码最大的价值在于它一次给了两套结构——CNN 和 LSTM还带了预训练词向量加载逻辑。也就是说你可以对比同样数据下两种结构的差异这本身就是大作业最好的加分项。3.1 词嵌入用预训练 Word2Vec 还是随机初始化情感分类通常是句子级任务词向量质量直接影响结果。源码里用的词向量是从原始语料训练出来的保存为 Word2Vec 格式。加载词向量后要把它转换成 Embedding 矩阵并固定行索引和 vocab 一致。# 加载 Word2Vec 并构建 embedding 矩阵 from gensim.models import Word2Vec w2v Word2Vec.load(data/hotel_w2v.model) embedding_matrix np.zeros((vocab_size, embed_dim)) # vocab_size 词表长度 for word, idx in word2idx.items(): if word in w2v.wv: embedding_matrix[idx] w2v.wv[word] else: embedding_matrix[idx] np.random.normal(0, 0.05, sizeembed_dim) # 在模型里通过 Embedding 层加载这里最关键的坑是词表索引与 Embedding 矩阵的行必须一一对应否则模型预测效果是零另一个细节是未命中词向量要用小方差随机初始化不能全补零否则模型学不出来。原项目把 Word2Vec 训练这一步封装好并缓存到了 data 目录你直接用即可。3.2 CNN 模型设计卷积核尺寸不是随便定的数据分析阶段确定 max_len70 之后CNN 模型采用多卷积核文本卷积结构卷积核尺寸分布在 3、4、5 附近池化后用拼接向量接全连接分类输出维度为 1 或 2 取决于损失函数选择。# TextCNN 建模核心结构代码 def build_textcnn(vocab_size, embed_dim, max_len, embedding_matrix): model Sequential([ Embedding(vocab_size, embed_dim, weights[embedding_matrix], trainableTrue, input_lengthmax_len, mask_zeroFalse), Conv1D(128, 3, activationrelu), # 每个卷积核尺寸 GlobalMaxPooling1D(), # 提取最强信号 Dense(64, activationrelu), Dropout(0.5), # 防过拟合 Dense(2, activationsoftmax) # 二分类 ]) return model # 编译用 Adam sparse categorical crossentropyConv1D 对序列做局部 n-gram 特征提取GlobalMaxPooling1D 保留最大特征Dropout 0.5 是经验值如果发现过拟合可以加大到 0.6欠拟合降到 0.3——这是文本分类里最常用的调节手段。这里 Embedding 层 trainableTrue 表示在训练时微调词向量。3.3 LSTM 模型设计序列建模的补位对比CNN 抓局部特征LSTM 抓长距离语义。酒店评论有些话拐弯抹角比如“房间不大但很干净”这时候长依赖建模有优势。LSTM 模型结构在源码里是 Embedding 加 LSTM 层再接全连接层输出。# BiLSTM 建模源码升级用法 model Sequential([ Embedding(vocab_size, embed_dim, weights[embedding_matrix], trainableTrue, input_lengthmax_len), Bidirectional(LSTM(64, return_sequencesFalse)), # 双向增强语义捕获 Dropout(0.5), Dense(32, activationrelu), Dense(2, activationsoftmax) ])双向 LSTM 的好处是同时看到上文和下文对中文短文本往往比单向效果好但缺点是训练更慢。源码里的 LSTM 是单向还是双向不重要重要的是对比实验说明卷积网络更关注关键词信号LSTM 关注语义整体性。你在答辩时要能讲清这两者的区别。3.4 训练观测点loss、val_acc 和早停策略训练过程要留痕。源码里用 ModelCheckpoint 保存最优权重同时配合 EarlyStopping 防止无限跑下去。对课程设计而言这两行逻辑是你“专业度”的证明。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ ModelCheckpoint(model/cnn/best_model.h5, monitorval_accuracy, save_best_onlyTrue, verbose1), EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue) ] model.fit(x_train_pad, y_train, validation_data(x_val_pad, y_val), epochs30, batch_size64, callbackscallbacks)ModelCheckpoint 选择验证集准确率最高的那一轮保存EarlyStopping 如果连续 5 个 epoch 验证集 loss 不降就提前终止并恢复最优权重。跑完看两个点val_accuracy 是否在 0.9 附近、有没有严重的过拟合两者都有答案后再汇报得分。4. 从原始数据到结果报告用一个脚本贯通全流程搞清楚模型链路后要能从零到一跑出结果。源码的价值在于“一站式”从文本文件到分类报告都有脚本衔接不是手动一步步复制粘贴。4.1 训练主流程的分步操作实践顺序是建议按下面四步走先跑通再改参。# 第 1 步确认依赖 pip install tensorflow2.10 jieba gensim pandas scikit-learn openpyxl # 第 2 步训练 LSTM python train_lstm.py # 第 3 步训练 CNN python train_cnn.py # 第 4 步评估模型效果 python score_report.py四个脚本串起来后查看输出目录下生成的 best_model.h5、训练日志、分类报告 txt 或 png。需要强调训练脚本在源码中可能命名不同README 没写清楚就列出目录文件逐一查看每个以 train 开头的脚本对应一个模型。4.2 参数怎么调整从基线出发做小步改动不建议直接调一大堆超参数。第一遍先还原基线max_len 70embedding 维度 128LSTM 单元 128batch size 64epoch 20-30早停 patience 5。一条评价准确率 0.9 左右就是正常水平。想提升效果优先动的是词向量来源和超参数敏感项停用词表扩充、去掉低频词、加大 batch size 或调低学习率。每次只改一个变量记录在 README 里。常见做法是写张表格记录版本、参数、准确率、备注这比任何解释都让老师信服。4.3 数据量有限时如何优化预训练词向量和交叉验证深度学习最怕数据少。酒店评论数量如果只有几千条CNN 可能比 LSTM 更稳。这时可以考虑加载预训练中文词向量比如网上公开的腾讯中文词向量替换原有随机初始化一举两得。另一个思路是做 K-Fold 交叉验证把数据进行 5 折切分每折训练再取平均指标作为鲁棒性实验添加到报告。5. 避坑与常见问题排查从环境依赖到模型质量这四关我都替你踩过运行这份源码绝大多数时间会花在环境配置和形状对齐上。以下四类问题出现的频率最高每条都按“现象 → 原因 → 解决”写清照着做能省半天。5.1 TensorFlow 版本导致报错类型不匹配、API 废弃现象执行 train 脚本时提示module tensorflow has no attribute placeholder或者Input tensors to a Model must come from tf.keras.Input。原因源码基于 TensorFlow 2.x 编写但如果你用 1.x 或 2.5 以下/以上的差异版本运行部分 API 表现不一致常见于 Adam 参数和 keras 导入路径。解决统一用 TensorFlow 2.10直接pip install tensorflow2.10。这个版本兼容性最稳对 PC 类 CPU 训练也够用。装好之后在 Python 里执行import tensorflow as tf; print(tf.__version__)确认一下如果输出的是 2.10.0说明环境没问题。5.2 维度对不上Embedding 矩阵和词表索引错位现象模型能加载预测结果全是同一类或者 loss 不下降val_acc 卡在 50% 附近。原因词向量矩阵的行索引和 word2idx 字典不一致或者 label 传进去的是二维 one-hot、模型输出却是 2 类 softmax导致计算 nan还有常见的就是 padding 位置用了 [0]但 Embedding 层的 mask_zeroFalse模型把 padding 当成了有意义的词。解决用脚本检查词汇对齐# 检查词表与 embedding 矩阵维度是否匹配 assert len(word2idx) embedding_matrix.shape[0], 词表大小与矩阵行数不一致 assert embedding_matrix.shape[1] embed_dim, 维度不匹配 print(f词表:{len(word2idx)}, 矩阵:{embedding_matrix.shape})加上这种断言第一时间暴露问题。判断模型输出是哪一维再决定标签怎么处理我一般在训练前打印 y_train 的 shape 和数值范围。5.3 训练速度太慢或内存爆掉现象使用 GPU 加速的机器正常纯 CPU 训练时 LSTM 跑一个 epoch 要 10 分钟以上数据量大时内存报错。原因max_len 设置过长、Embedding 矩阵太大或 batch size 过大导致中间张量占用太高。解决CPU 环境优先训练 CNN 而不是 LSTM——这是结构差异决定的CNN 可以并行计算但 LSTM 必须逐步计算。调低 max_len 到 50batch_size 降到 32对过大的数据做截断时不从头加载整个语料而是先抽样估算词表大小。5.4 预测结果全为 0 或全为 1现象测试集上准确率极高但实际输入新句子预测结果全部是同一个类别没有任何区分度。原因最常见是训练标签反了——score 1 变成负而 4 变成正这类映射写反了其次是训练集本来就极度不平衡95% 正向模型学到只输出多数类就能拿高分。解决打印分类报告看精确率和召回率的分布如果负样本 precision 是 0那就是数据问题重新检查映射逻辑并用df[label].value_counts()检查平衡度不平衡就过采样负例或改用 weighted loss。注意判断情感分析系统是否过关不能只看准确率。用一句“房间很吵但位置很好”测真实判断力分类器会基于经验把它分到哪个类你才有依据继续调。6. 把这份源码变成你自己的作品一个 15 分钟内见效的改进实验到这一步项目已经能跑通但它还是“别人的作品”。答辩和后续展示的真正分水岭是你能不能基于现有代码做一个小而完整的改进实验并清晰说明结果。6.1 方法一追加测试样本验证真实泛化能力源码自带的验证集是从原始数据切出来的随机采样分布一致模型得分会偏高。我的习惯是从网上抓取或手动构造 50 条全新评论一半正一半负写成一个 csv重新调用 score_report 脚本测试。python evaluate_custom.py --input my_test.csv --model model/lstm/best_model.h5如果自定义集上准确率比原始验证集低 5 个点以上说明项目参数过拟合到了原数据分布这是一个极有价值的发现把它写进报告模型在多大范围内有效、什么情况下失效。老师会觉得你的工作量超预期。6.2 方法二对比基线词向量的影响基线跑通后把你随机初始化的词向量替换成预训练向量用同一套数据训练同样的模型记录两组准确率和 F1。这一步其实能翻出“词向量对中文短文本情感分类是否有决定性贡献”的结论文档里用表格说明即可。模型词向量类型准确率F1 得分TextCNN随机初始化0.890.88TextCNN 预训练词向量腾讯中文0.920.91实现方式是在 data 预处理里新增一个加载函数判断是否传入预训练词向量文件其余代码不动。这是我个人最推荐的改进方向投入半小时但效果直观。6.3 调用训练好的模型封装 Demo现场演示预测只跑脚本不够“系统”我当时把推理部分封装成一个函数再包一行命令交互式运行。核心思路是把预处理 pipeline 写在 predict 里而不是手写新逻辑# 将文本预测封装为可见函数 def predict_sentiment(text): seq text_to_seq(text, word2idx, max_len70) seq_pad np.array([seq]) prob model.predict(seq_pad)[0] label int(prob.argmax()) return 正面 if label 1 else 负面, max(prob) print(predict_sentiment(房间很干净服务也很好下次还会来)) print(predict_sentiment(位置偏隔音差洗澡水忽冷忽热))函数先走与训练完全一致的分词和序列化逻辑再预测最后输出最大概率值。在一次现场演示中输入几条新评论看预测结果和置信度既显得真实又能用来解释模型是“如何决策”的。如果打印的置信度普遍低于 0.8说明模型对某些语义还敏感这也是改进点。自从我用这份源码做完课程设计养成了一个习惯拿到任何一个 NLP 项目先追数据管线再做对比实验最后才谈模型创新。顺序反了再好的网络也救不回来。希望这个拆解对你有用。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进