BERT 微调电商评论情感分类实战:95.04% 准确率,预训练微调 vs 从零训练 LSTM

项目地址(Gitee):https://gitee.com/Touari/bert_sentiment_2class.git
项目定位:基于预训练模型 bert-base-chinese 微调电商评论情感二分类器,与已完成的 LSTM 情感分类项目使用同一数据集、同一任务,对比"预训练微调"与"从零训练 LSTM"两条技术路线
技术栈:Python 3.12 · PyTorch 2.7.1+cu118 · transformers 5.15.1 · datasets 5.0.1
所有数据均为实测(2026-08-22,RTX 3060 Laptop 6GB)


一、项目概述

  • 任务:电商评论情感二分类(正面 / 负面)
  • 模型路线:BERT 预训练编码器(1.02 亿参数)+ 线性分类头(BCE 路线,输出 1 个 logit)
  • 实测结果:测试集准确率 95.04%(LSTM 项目为 91.48%)

💡 这个项目的独特价值:与作者此前从零训练的 LSTM 情感分类项目(lstm_sentiment_2class同数据集、同任务、同 SEQ_LEN,是"预训练微调 vs 从零训练"最公平的对照实验。

技术栈与版本(实测)

项目 版本 / 配置
Python 3.12.0
PyTorch 2.7.1+cu118
transformers 5.15.1
datasets 5.0.1
pandas / scikit-learn 3.0.2 / 1.8.0
GPU RTX 3060 Laptop 6GB(driver 595.79)

二、模型架构

总参数量 102,268,417(≈1.02 亿),实测构成:

模块 参数量 占比 说明
BERT 编码器(bert-base-chinese) 102,267,648 99.999% 12 层 Transformer,hidden 768,12 头
线性分类头 769 0.001% 768 → 1(权重 768 + 偏置 1)

AutoTokenizer

评论文本
(定长128)

input_ids + attention_mask + token_type_ids
[batch, 128]

BERT 编码器
12层 Transformer × 768d
(1.02亿参数, 预训练权重)

last_hidden_state
[batch, 128, 768]

取 [CLS] 向量
[:, 0, :]
[batch, 768]

Linear(768→1)
分类头

logit
[batch]

sigmoid > 0.5
正面 / 负面

关键设计:为何取 [CLS]

BERT 输入序列首尾各有一个特殊 token:[CLS](开头)和 [SEP]。在预训练(Masked LM + Next Sentence Prediction)中,[CLS] 位置的输出被训练为聚合整句语义的表示向量,因此分类任务直接取 last_hidden_state[:, 0, :] 即可,无需 LSTM 式的最后时刻/池化操作。

三输入机制

BERT 需要三个输入张量(与 LSTM 只送 token ids 不同):

输入 作用
input_ids 词表索引(bert-base-chinese 词表 21128 词,=0 / =100 / [CLS]=101 / [SEP]=102
attention_mask 1 表示真实 token,0 表示 padding,屏蔽无关位置的注意力
token_type_ids 句子对区分(单句任务全 0)

课程代码沿用 LSTM 项目的 SEQ_LEN=128 定长策略:padding='max_length' + truncation=True,与 LSTM 项目输入规格完全对齐,便于公平对比。


三、数据与预处理(实测)

环节 数值 / 说明
原始数据 online_shopping_10_cats.csv62774 条(含表头),字段:cat(10 类目)/ label(0 负 / 1 正)/ review
过滤 去除 cat 列;过滤空评论
标签 ClassLabel(names=['negative','positive'])
划分 train_test_split(test_size=0.2, stratify_by_column='label')train 50218 条 / test 12555 条
编码 AutoTokenizer(bert-base-chinese),padding='max_length'truncation=Truemax_length=SEQ_LEN=128
产物 data/processed/:train/test 各 1 个 arrow 分片 + dataset_dict.json(约 48M)
预处理实测耗时 数据编码 ~4 秒(50218 条)/ ~1 秒(12555 条),全流程约 1~2 分钟

raw CSV
62774条

过滤cat列/空评论

80/20 分层划分
train 50218 / test 12555

AutoTokenizer 编码
pad+truncate 到 128

data/processed
arrow 数据集(48M)

Dataset(datasets)
set_format('torch')

DataLoader
batch=26, shuffle

模型训练/评估


四、训练配置与流程

配置项 说明
SEQ_LEN 128 定长编码
BATCH_SIZE 26 本项目调参值(课程默认 16)
LEARNING_RATE 1e-5 BERT 微调标准学习率(远小于常规 1e-3)
EPOCHS 2 快速验证配置,正式训练可调回 20
优化器 Adam(1e-5) 全参数微调(BERT 与分类头一起训练)
损失 BCEWithLogitsLoss 输出单 logit,与 LSTM 项目同路线
设备 CUDA(RTX 3060 Laptop 6GB) torch.cuda.is_available() 自动选择

训练流程(train.py):

  1. 加载数据、tokenizer、模型、损失、优化器
  2. SummaryWriter 写 TensorBoard(logs/时间戳/
  3. 每 epoch:train_one_epoch 遍历全部 batch → 前向 → 计算 loss → backward → step → zero_grad
  4. loss 创新低 → 保存 models/best.pt(409M)
  5. TensorBoard 记录 Loss 曲线

五、实验结果(实测)

5.1 训练损失

最后一次训练(logs/2026-08-22_11-22-31),共 2 个 epoch:

Epoch Loss
1 0.1777
2 0.1174

Loss 快速下降(BCE loss 0.5 为随机水平,接近 0.12 说明学习良好)。共进行 9 次训练实验(11:06-11:22),最终 best.pt 保存于 12:37。

5.2 测试集评估

evaluate.py 实测(best.pt,test 12555 条):

指标 数值
准确率 acc 95.04%(0.9504)
评估耗时 ~59 秒(483 个 batch,约 8 it/s)
判定规则 sigmoid > 0.5 → 正向

5.3 与 LSTM 项目对比(同数据集、同任务、同 SEQ_LEN)

维度 LSTM 项目 BERT 项目(本项目)
模型 自建 Embedding + LSTM 预训练 bert-base-chinese
参数量 ~几十万(自建) 102,268,417
句子表示 最后时刻 hidden [CLS] 向量
分类头 Linear(128→1) Linear(768→1)
训练时长(2 epoch) 秒/分钟级 数分钟~十余分钟(9 次实验 16 分钟内完成)
测试集准确率 91.48% 95.04%
思路 从零学特征 站在巨人肩膀上微调

结论:同样 2~3 epoch 级别的小规模训练下,BERT 预训练微调比从零训练的 LSTM 高出约 3.5 个百分点,且无需调参即可达到高质量;代价是参数量大 2~3 个数量级、训练耗时与显存需求(约 3.5GB)明显更高。


六、推理与应用

predict.py 交互式命令行:

> 这款手机性价比很高,用了半年也没问题
正向(置信度:0.98x)

> 质量太差,物流也慢,非常失望
负向(置信度:0.96x)
  • 单条推理流程:tokenizer(text, padding='max_length', truncation=True, max_length=128, return_tensors='pt') → 模型前向 → sigmoid → 概率输出
  • 批量推理 predict_batchmodel.eval() + torch.no_grad(),评估模块复用

七、踩坑记录

7.1 预处理报错 OSError:模型名称非法

  • 现象process.py 运行报 OSError: Repo id must use alphanumeric chars...
  • 根因pretrained/bert-base-chinese 目录缺失(模型未拷贝),且代码使用 local_files_only=True 强制离线;transformers 在路径不存在时将其误判为模型名并校验其字符规范性
  • 解决:从课程目录拷贝完整 pretrained/bert-base-chinese/(5 个文件)到项目
  • 教训from_pretrained 传入本地路径只读本地不下载;传模型名才自动下载(下载结构为缓存哈希目录)

7.2 multiprocess 兼容性警告(Python 3.12)

  • 现象:预处理结束打印 Exception ignored in: ResourceTracker.__del__ ... AttributeError: '_thread.RLock' object has no attribute '_recursion_count';退出码仍为 0,不影响结果
  • 根因:multiprocess 0.70.19(已停更)在 Python 3.12(移除 RLock._recursion_count)下不兼容,仅影响进程退出时的析构
  • 解决:给 venv 的 site-packages/multiprocess/resource_tracker.py 第 102 行打补丁:self._lock._recursion_count()getattr(self._lock, '_recursion_count', lambda: 0)()
  • 注意:若重建 venv 需重打该补丁

7.3 import config 报 ModuleNotFoundError

  • 现象:PyCharm 运行 dataset.pyNo module named 'config'
  • 根因:平级模块导入依赖 sys.path 包含 src/;PyCharm 运行配置默认工作目录为项目根,不会自动把脚本目录加入 sys.path
  • 解决:命令行先 cd src 再运行;PyCharm 中将 src 标记为 Sources Root

7.4 误删文件事故(2026-08-22)

  • 现象:验证脚本使用真实文件路径 touch/unlink,误删 src/config.pydata/raw/*.csvmodels/vocab.txtmodels/best.ptpretrained/bert-base-chinese/config.json
  • 解决:全部从课程目录恢复,md5 逐字节校验一致(含漏补的 config.py)
  • 教训git check-ignore 验证 .gitignore 无需文件真实存在(纯路径模式匹配);验证脚本一律使用假路径,绝不 touch/unlink 真实项目文件

7.5 课程代码遗留问题

  • from tokenizer import JiebaTokenizer(process/train)与 from src.tokenizer import JiebaTokenizer(evaluate/predict):均为死导入(分词实际使用 AutoTokenizer),已清理
  • predict.py 首行 import jieba:仍为死导入(jieba 已安装但未使用)
  • config.pyEMBEDDING_DIM / HIDDEN_SIZE:LSTM 项目残留,无任何文件引用,可删

八、Git 版本管理

10 条中文描述式提交:初始化 → tokenizer 本地离线 → dataset/model 模块 → 清理死导入×4 → 超参数调整 → 数据集入库。

约定:

  • 提交信息:中文描述式,标题 + 「模块职责 / 本次改动」正文(git commit -F 消息文件,UTF-8,避免 PowerShell GBK 乱码)
  • 按文件 / 逻辑逐批提交
  • 大文件一律不入库:pretrained/(393M)、models/*.pt(409M)、logs/data/processed/(48M)
git remote add origin https://gitee.com/Touari/bert_sentiment_2class.git
git push -u origin master
# Clash 代理未开启时直连推送:
git -c http.proxy= -c https.proxy= push -u origin master

九、运行方法

cd E:/workspace/PycharmProjects/BERT_Sentiment_2class/src
PY=E:/workspace/PycharmProjects/python_bootcamp/.venv/Scripts/python.exe

$PY -E process.py    # 预处理(产物在 data/processed)
$PY -E train.py      # 训练(TensorBoard 日志在 logs/,最优权重 models/best.pt)
$PY -E evaluate.py   # 评估(测试集准确率)
$PY -E predict.py    # 交互式预测(输入 q/quit 退出)

所有命令需在 src/ 目录下执行(平级导入);-E 防止系统 PYTHONPATH 污染。


十、下一步扩展方向

  1. 完整训练EPOCHS=20 + 学习率 warmup,预期 acc 可到 96%+
  2. 早停/验证集保存:以验证集 loss 或 acc 选择 best.pt(当前仅训练 loss)
  3. 混合精度torch.cuda.amp(fp16)可提速约 2 倍、省显存约一半
  4. 部署化:fastapi 接口化,纳入 Docker 容器化项目(与 LSTM 情感 API 组成 compose 栈)
  5. 对比实验:把 20 epoch 全量训练结果与 LSTM 91.48% 做完整对标

小结

两条技术路线的对照实验给出了清晰的答案:

  • 从零训练 LSTM:几十万参数,91.48%,需要手工设计词嵌入与网络结构
  • 预训练 BERT 微调:1.02 亿参数,95.04%,2 个 epoch 即可达到高质量

BERT 用 2~3 个数量级的参数换来了约 3.5 个百分点的提升和"免调参"的工程便利——这正是"预训练 + 微调"范式统治 NLP 的原因:与其从零学习语言知识,不如站在 1 亿参数预训练模型(海量语料学到的语言理解)的肩膀上,只学一个 769 参数的分类头。

而作者 NLP 学习路线至此也完成了完整闭环:RNN 输入法 → LSTM 情感 → Seq2Seq 翻译 → Seq2Seq+Attention → Seq2Seq+Transformer → BERT 微调,从"自己搭网络"走到了"站在巨人肩膀上"。

完整代码与数据见项目仓库:https://gitee.com/Touari/bert_sentiment_2class.git

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐