ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

基于Bert与CNN/RNN/RCNN的购物评价情感分析源码实战

基于Bert与CNN/RNN/RCNN的购物评价情感分析源码实战

简介:这份资源面向计算机、人工智能、数据科学等专业的在校学生与教师,以及需要完成毕业设计、课程设计或期末大作业的学习者,提供一套基于Python的购物平台评价情感分析系统源码。项目以Bert预训练模型为核心,融合CNN、RNN、RCNN、DPCNN等多种网络结构,并包含ERNIE相关实现,可用于对比不同算法在中文评论情感分类任务上的表现,帮助读者理解预训练语言模型与深度网络结合的完整流程。压缩包共55个文件,以py源码与pyc编译文件为主,另含md说明文档及一个zip子包,整体约503KB,目录中涵盖模型定义、训练脚本、工具函数与预训练模型加载模块,结构清晰便于按模块阅读。目前已有265人学习关注。读者可据此掌握数据预处理、模型搭建、训练调参与评估的完整链路,并在此基础上修改扩展,形成自己的情感分析应用或毕设方案。

1. 从一份能跑通的购物评价情感分析源码说起

电商后台每天沉淀几万条评价,人工翻页看不过来,老板又想知道「这周差评到底集中在哪」。这时候一套能直接跑起来的情感分析代码,比十篇讲注意力机制的论文都管用。这份资源就是干这个的:Python 写的购物平台评价情感分析系统,把 Bert 和 CNN、RNN、RCNN 拼在一起做多模型对比,还额外带了 ERNIE 系列的几个变体。目录里models文件夹下躺着BertCNN.py、BertRNN.py、BertRCNN.py、BertDPCNN.py、ERNIEDPCNN.py这些文件,train.py负责训练调度,utils.py管数据加载和预处理,pytorch_pretrained是那份经典的预训练权重加载工具包。适合谁?正在做毕设、课程大作业,或者想拿一个完整情感分析 pipeline 改造成自己业务场景的人。它不是一个空壳 demo,模型文件、训练入口、工具函数都在,拿到手改改数据路径就能跑。

2. Bert 加 CNN/RNN/RCNN:多模型并行的工程结构怎么搭

2.1 为什么不是只用一个模型

单模型跑情感分析,准确率卡在某个数上不去是常态。Bert 本身已经很强,但它输出的是每个 token 的上下文向量,直接取[CLS]做分类,等于把序列里的局部特征全扔了。CNN 擅长抓 n-gram 级别的局部情感词组合,比如「质量不错」和「质量不怎么样」这种差一个字的反转;RNN 擅长建模长距离依赖,比如「虽然发货慢,但是东西真的好用」这种转折句。RCNN 就是把两者串起来,先 RNN 再 CNN,兼顾顺序和局部。这份源码把这几条路都铺好了,你可以直接对比同一份数据下哪个结构更贴合你的评价语料。

从工程角度看,多模型并行不是让你同时跑四个模型,而是共用一套数据加载和训练循环,通过配置切换模型类。train.py里通常会有一个模型选择参数,指向models下不同的类。这样你调参的时候不用改数据管道,只换模型名就行。

2.2 目录结构与模块职责

拿到压缩包解压后,核心目录长这样:

├── models/ │ ├── Bert.py │ ├── BertCNN.py │ ├── BertRNN.py │ ├── BertRCNN.py │ ├── BertDPCNN.py │ ├── ERNIE.py │ └── ERNIEDPCNN.py ├── pytorch_pretrained/ │ ├── modeling.py │ ├── tokenization.py │ ├── optimization.py │ └── file_utils.py ├── train.py ├── utils.py ├── main.py └── 介绍.md

pytorch_pretrained是 HuggingFace 早期版本的预训练加载库,modeling.py里定义了 BertModel 的前向逻辑,tokenization.py负责把中文句子切成 Bert 能吃的 token。utils.py一般包含数据集类、评价指标计算、日志打印。train.py是训练主循环,main.py可能是推理入口或者参数解析入口。介绍.md里通常写了环境依赖和运行命令,先读它。

2.3 模型类的组织方式

以BertCNN.py为例,它的结构通常是:Bert 做编码器,取最后一层的last_hidden_state,然后接一个卷积层。卷积核尺寸一般设 2、3、4 三种,每种 128 或 256 个 filter,做完 max-pooling 后拼接,最后过全连接分类。BertRNN.py则是把 Bert 输出喂给 LSTM 或 GRU,取最后一个时间步的 hidden state 或者做平均池化。BertRCNN.py先过 BiLSTM,再把 LSTM 每个时间步的输出当成序列送进 CNN。

这些模型类的接口应该保持一致:forward(input_ids, attention_mask, token_type_ids)返回 logits。这样train.py里才能用同一套训练代码切换模型。如果你要加自己的模型,照着这个接口写就行。

提示:pytorch_pretrained这个包比较老,和新版 transformers 不兼容。如果你环境里已经装了 transformers,注意别让 import 冲突。常见做法是建一个独立虚拟环境,只装这个项目需要的依赖。

3. 把源码跑起来:环境、数据、训练三步走

3.1 环境准备与依赖安装

这份源码基于 PyTorch,Bert 权重加载依赖pytorch_pretrained。建议 Python 3.7 或 3.8,PyTorch 1.7 到 1.10 之间比较稳。太新的 PyTorch 可能和pytorch_pretrained里的某些 API 不兼容。

# 创建独立环境,避免和已有 transformers 冲突 conda create -n senti_bert python=3.8 conda activate senti_bert # 安装 PyTorch,根据你的 CUDA 版本选对应命令 pip install torch==1.9.0+cu111 torchvision==0.10.0+cu111 -f https://download.pytorch.org/whl/torch_stable.html # 安装项目依赖 pip install numpy pandas scikit-learn tqdm

pytorch_pretrained通常已经包含在项目目录里,不需要额外 pip 安装。如果你在train.py里看到from pytorch_pretrained import BertModel, BertTokenizer,说明它用的是本地包。确认一下pytorch_pretrained/__init__.py存在,否则 import 会报错。

3.2 数据格式与加载逻辑

购物评价情感分析通常是二分类(好评/差评)或三分类(好评/中评/差评)。utils.py里一般会定义一个Dataset类,读取一个 CSV 或 TSV 文件,列名常见的是text和label。数据加载的核心逻辑是:用BertTokenizer把文本转成input_ids、attention_mask、token_type_ids,然后 padding 到统一长度。

# utils.py 中典型的数据处理片段 from pytorch_pretrained import BertTokenizer class SentimentDataset(Dataset): def __init__(self, file_path, tokenizer, max_len=128): self.data = pd.read_csv(file_path) self.tokenizer = tokenizer self.max_len = max_len def __getitem__(self, index): text = str(self.data.iloc[index]['text']) label = int(self.data.iloc[index]['label']) # encode 返回 input_ids 和 token_type_ids tokens = self.tokenizer.tokenize(text) tokens = ['[CLS]'] + tokens + ['[SEP]'] input_ids = self.tokenizer.convert_tokens_to_ids(tokens) # padding 到 max_len input_ids = input_ids[:self.max_len] + [0] * (self.max_len - len(input_ids)) attention_mask = [1 if i != 0 else 0 for i in input_ids] token_type_ids = [0] * self.max_len return { 'input_ids': torch.tensor(input_ids), 'attention_mask': torch.tensor(attention_mask), 'token_type_ids': torch.tensor(token_type_ids), 'label': torch.tensor(label) }

max_len设 128 对大多数购物评价够用,长评论可以设 256,但显存占用会翻倍。padding用 0 填充,attention_mask标记哪些位置是真实 token。如果你的数据里标签是「正面/负面」这种字符串,需要在 Dataset 里做一层映射,转成 0 和 1。

3.3 训练脚本的关键参数

train.py里通常有几个必须关注的参数:batch_size、learning_rate、epochs、model_name。Bert 微调的学习率一般设 2e-5 到 5e-5,太大容易把预训练权重冲垮,太小收敛慢。batch_size在 16 到 32 之间,取决于你的显存。

# 典型训练启动命令 python train.py \ --model_name BertCNN \ --data_path ./data/reviews.csv \ --batch_size 16 \ --learning_rate 2e-5 \ --epochs 5 \ --max_len 128 \ --save_dir ./checkpoints

model_name对应models文件夹下的类名,改成BertRNN或BertRCNN就能切换模型。save_dir是模型保存路径,训练完会在里面生成pytorch_model.bin和config.json。如果你要对比多个模型,建议每个模型单独跑一次,把结果记到表格里。

注意:第一次运行会下载 Bert 预训练权重,如果网络不通会卡住。常见做法是提前把bert-base-chinese的权重下好,放到pytorch_pretrained能识别的缓存目录,或者改代码从本地路径加载。

4. 避坑与排查:跑这份源码时最容易翻车的五个地方

4.1 现象:import pytorch_pretrained 报错 ModuleNotFoundError

原因通常是项目目录里没有这个包,或者你从错误的路径运行脚本。pytorch_pretrained是本地目录,不是 pip 包。如果你在train.py所在目录运行,Python 会把当前目录加入sys.path,正常能 import。但如果你从其他目录用绝对路径运行,就可能找不到。

解决:确认pytorch_pretrained/__init__.py存在,然后在项目根目录下运行脚本。如果还是不行,在train.py开头加sys.path.append(os.path.dirname(os.path.abspath(__file__)))。

4.2 现象:训练 loss 不下降,准确率卡在 50%

原因可能是学习率太大,Bert 权重被破坏;也可能是数据标签没对齐,比如 CSV 里 label 列是字符串但代码按 int 读,全变成了 0。还有一种情况是attention_mask没传对,模型把 padding 位置也当成有效 token。

解决:先把学习率降到 1e-5 试一轮,观察 loss 是否下降。然后打印几条 Dataset 的输出,确认input_ids和label对应正确。检查attention_mask是否和input_ids长度一致,padding 位置是否为 0。

4.3 现象:显存溢出 CUDA out of memory

原因通常是batch_size太大或max_len太长。Bert 本身参数量大,加上 CNN 或 RNN 的额外参数,显存占用比纯 Bert 分类高不少。

解决:把batch_size降到 8 或 4,max_len从 256 降到 128。如果还不够,可以冻结 Bert 的前几层,只微调后几层和分类头。在models里找到 Bert 编码器部分,把前 6 层的requires_grad设为 False。

4.4 现象:模型在验证集上表现好,测试集上崩了

原因一般是数据泄露或分布不一致。比如验证集和训练集来自同一批用户,测试集是另一批;或者预处理时用了全局统计量,把测试集信息泄露了。

解决:检查数据划分是否随机,最好按时间划分,比如用前 80% 的评价训练,后 20% 测试。预处理步骤(如 padding 长度、标签映射)要基于训练集统计,然后应用到验证和测试集。

4.5 现象:切换模型后报维度不匹配

原因是你改了模型结构但没改分类头的输入维度。比如BertCNN的卷积输出维度是 384,你换成BertRNN后 LSTM 的 hidden 维度是 256,全连接层如果写死了 384 就会报错。

解决:在模型类里用self.fc = nn.Linear(hidden_dim, num_classes),hidden_dim根据实际输出动态计算。或者在train.py里加一个打印,看看每个模型输出的 shape 是多少,再对应改全连接层。

5. 进阶玩法:用 ERNIE 替换 Bert 并做模型融合

5.1 从 Bert 切到 ERNIE 的改动点

models文件夹里有ERNIE.py和ERNIEDPCNN.py,说明作者已经预留了百度的 ERNIE 预训练模型接口。ERNIE 在中文任务上通常比同尺寸 Bert 略好,因为它做了实体级掩码。切换的核心是换 tokenizer 和模型加载路径。

# 从 Bert 切到 ERNIE 的关键改动 from pytorch_pretrained import BertTokenizer, BertModel # 原来加载 bert-base-chinese # tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') # model = BertModel.from_pretrained('bert-base-chinese') # 换成 ERNIE 权重路径 tokenizer = BertTokenizer.from_pretrained('./ernie-1.0/vocab.txt') model = BertModel.from_pretrained('./ernie-1.0/pytorch_model.bin')

ERNIE 的 tokenizer 和 Bert 兼容,都是基于 WordPiece,所以tokenization.py不用大改。但 ERNIE 的权重文件命名可能不同,确认pytorch_model.bin和bert_config.json都在对应目录下。如果加载报错,检查modeling.py里的from_pretrained是否支持你给的路径格式。

5.2 多模型结果融合的简单做法

如果你把 BertCNN、BertRNN、BertRCNN 都训了一遍,每个模型在测试集上都有预测概率,可以做一个加权平均。权重按验证集准确率分配,准确率高的模型权重大。

# 多模型概率融合 import numpy as np # 假设三个模型输出的概率矩阵 shape 都是 (n_samples, 2) probs_cnn = np.load('probs_bertcnn.npy') probs_rnn = np.load('probs_bertrnn.npy') probs_rcnn = np.load('probs_bertrcnn.npy') # 按验证集准确率设权重 w_cnn, w_rnn, w_rcnn = 0.35, 0.30, 0.35 final_probs = w_cnn * probs_cnn + w_rnn * probs_rnn + w_rcnn * probs_rcnn predictions = np.argmax(final_probs, axis=1)

融合通常能比单模型提升 1 到 2 个百分点,但前提是模型之间的错误不完全重叠。如果三个模型都在同一批样本上犯错,融合也没用。建议先看混淆矩阵,确认模型互补性。

5.3 验证融合是否有效的正确姿势

不要直接在测试集上调权重,那样会过拟合。正确做法是:训练集训模型,验证集调融合权重,测试集只跑一次最终评估。如果验证集太小,可以用交叉验证,把数据分成 5 折,每折训一个模型,最后平均。

我一般会先跑单模型,记录每个模型在验证集上的准确率和 F1,然后按 F1 分配权重。如果某个模型明显拖后腿,直接踢掉,不用强行融合。从那以后我每次做多模型对比,都强制先看单模型混淆矩阵,再决定要不要融合。希望帮到你。

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

RELATED READING

延伸阅读

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