Skip to content

Repository files navigation

BERT 文本分类 (BERT Text Classification)

Python Transformers License

基于 BERT 的工业级文本分类项目。使用 Hugging Face transformers 的 Trainer API 进行训练, 支持完整的训练 → 评估 → 推理流程,并通过 TensorBoard 统一记录训练过程和评估指标。

核心设计: device: auto 一键切换 CPU / GPU,代码自动检测并调整所有设备相关参数, 保持训练动力学一致(effective batch 恒定),彻底消除 CPU 和 GPU 之间的精度差异问题。


项目特点

  • 设备自动切换device: auto 自动检测 CPU/GPU,智能调整 batch_size、fp16、多线程等参数,无需手动修改配置
  • CPU / GPU 精度一致 — 保持 effective batch 恒定(16),消除因批量变化导致的精度差异
  • 模块化解耦 — 配置、数据处理、模型、工具、入口脚本职责清晰,易于理解和修改
  • Trainer API — 使用 Hugging Face 原生训练接口,无手写训练循环
  • 动态 Padding — 使用 DataCollatorWithPadding 按批次动态填充,节省计算资源
  • 自动标签推断 — 从数据文件中自动扫描并生成标签映射,无需手动硬编码
  • 编码防御 — 显式指定 UTF-8 编码读取 CSV,防止 Windows 中文乱码导致 num_labels=1
  • TensorBoard 全流程覆盖 — 训练过程的 loss / 梯度 / 学习率 / 评估指标,以及最终的测试指标,全部写入 TensorBoard
  • 跨平台可复现 — 使用 Python 内置 random.Random 做数据划分,摆脱不同平台 numpy 版本差异导致的划分不一致

目录结构

bert-text-classification/
├── config/
│   └── config.yaml              # 全局配置文件(所有可调参数)
├── data/
│   ├── __init__.py
│   └── processor.py             # 数据加载、标签推断、分词、划分
├── models/
│   ├── __init__.py
│   └── model.py                 # BERT 模型构建 + 冻结策略
├── utils/
│   ├── __init__.py
│   ├── metrics.py               # 评估指标(F1、准确率)
│   ├── tensorboard_callback.py   # TensorBoard 回调(训练+评估日志)
│   └── logger.py                # 日志配置
├── tests/
│   ├── __init__.py
│   ├── test_dataset.py          # 数据集单元测试
│   ├── test_model.py            # 模型前向传播测试
│   └── test_metrics.py          # 评估指标测试
├── train.py                     # 训练入口
├── evaluate.py                  # 评估入口
├── predict.py                   # 推理入口
├── requirements.txt             # Python 依赖
├── .gitignore                   # Git 忽略规则
└── README.md                    # 项目说明

模型效果tensorboard

alt text alt text

环境准备

安装依赖

pip install -r requirements.txt

主要依赖:

包名 用途
torch 深度学习框架
transformers BERT 模型和分词器、Trainer API
datasets 数据加载和预处理
scikit-learn 评估指标(F1、准确率、分类报告)
pyyaml 配置文件解析
tensorboard 训练和评估日志可视化
pytest 单元测试

数据格式

数据文件为 CSV 格式(逗号分隔),无表头,每行两列:

文本内容,标签名
控制卡故障影响系统启动吗?,控制卡故障
换卡后怎么确认故障解决了?,bad card类故障
换盘后怎么确认RAID组正常了?,硬盘故障

默认路径:./datas/text_classify/intention/train.csv

编码要求: 文件必须是 UTF-8 编码(建议带 BOM,代码已自动处理)。代码显式指定 encoding="utf-8" 读取,避免 Windows 上 GBK 解码导致中文标签乱码。

配置文件

所有可调参数集中在 config/config.yaml 中,但大多数情况下你只需要设置模型路径

device: auto  # ← 自动检测 CPU/GPU,无需手动切换

model:
  pretrained_model_name_or_path: D:\huggingface\bert-base-chinese  # 改为你的路径

training:
  num_train_epochs: 20
  learning_rate: 5.0e-5
  freeze_bert: false  # 冻结编码器,仅训练分类头

其他参数(batch_size、fp16、梯度累积、CPU 线程数等)代码会自动根据设备调整

提示: 如果你使用 Linux,修改 config.yamlmodel.pretrained_model_name_or_path 为你的路径,如 /opt/work/bert-base-chinese


使用指南

1. 训练

python train.py
# 或指定配置文件 + 从 checkpoint 恢复
python train.py --config ./config/config.yaml --resume checkpoint-500

训练过程中会自动:

  1. train.csv80% / 10% / 10% 划分训练集、验证集、测试集(划分索引缓存到文件,跨平台可复现)
  2. 自动扫描数据中的所有标签,生成 label2id / id2label 映射
  3. 自动检测 CPU / GPU,调整 batch_size、fp16、梯度累积步数
  4. 每个 epoch 在验证集上评估,以 F1 为指标保存最佳模型
  5. 训练和评估指标通过 MetricsCallback 实时写入 TensorBoard
  6. 训练结束后在测试集上做最终评估并输出结果
  7. 最佳模型和分词器保存至 ./output/bert-finetuned-intent-textclassify/best_model/

启动日志示例:

2026-07-21 23:12:41 [INFO] 配置文件加载完成: ./config/config.yaml
2026-07-21 23:12:41 [INFO] ========================================================
2026-07-21 23:12:41 [INFO] 设备检测: CPU
2026-07-21 23:12:41 [INFO] 配置来源: auto
2026-07-21 23:12:41 [INFO]   no_cuda: true | fp16: false
2026-07-21 23:12:41 [INFO]   batch_size (train/eval): 4 / 4
2026-07-21 23:12:41 [INFO]   gradient_accumulation_steps: 4
2026-07-21 23:12:41 [INFO] ========================================================
2026-07-21 23:12:41 [INFO] CPU 多线程优化 — 并行线程数: 8
2026-07-21 23:12:41 [INFO] 全局随机种子已设置: 42
2026-07-21 23:12:41 [INFO] 加载分词器: D:\huggingface\bert-base-chinese
2026-07-21 23:12:41 [INFO] 加载数据文件: ./datas/text_classify/intention/train.csv
2026-07-21 23:12:41 [INFO] 原始样本总数: 1131
2026-07-21 23:12:41 [INFO] 前 5 条样本标签: ['控制卡故障', 'bad card类故障', '硬盘故障', ...]
2026-07-21 23:12:41 [INFO] 自动检测到 19 个类别: ['DDR mbit error', 'IMU error', ...]

2. 评估

python evaluate.py
# 或指定模型路径
python evaluate.py --model_path ./output/bert-finetuned-intent-textclassify/best_model

评估脚本会:

  1. 加载训练好的最佳模型
  2. 在测试集上进行预测
  3. 计算多维度指标(F1 micro / macro、准确率、精确率、召回率、各类别 F1)
  4. 将最终评估指标写入 TensorBoard(与训练曲线并列显示)
  5. 输出详细的分类报告混淆矩阵

3. 推理

# 单条文本预测
python predict.py --text "控制卡故障影响系统启动吗?"

# 批量预测
python predict.py --input_file test_texts.txt --output_file results.csv

输出示例:

========================================================
预测结果
========================================================
输入文本: 控制卡故障影响系统启动吗?
预测类别: 控制卡故障 (ID: 0)
置信度:   0.9876

各类别概率:
          控制卡故障: 0.9876  ████████████████████████████████████████████████
         bad card类故障: 0.0012
              硬盘故障: 0.0010
          DDR mbit error: 0.0002
              IMU error: 0.0001
                ...

4. 运行测试

pytest tests/ -v

设备自动切换机制

一句话原理

device: auto  # ← 这是唯一的"开关"

device: auto 时,train.py 中的 auto_configure_device() 自动检测并设置所有设备相关参数:

参数 CPU 模式 GPU 模式
no_cuda / use_cpu true false
fp16 混合精度 false true
per_device_train_batch_size 4 8
per_device_eval_batch_size 4 8
gradient_accumulation_steps 4 2
effective batch 4×4=16 8×2=16
CPU 多线程 os.cpu_count() 不设置

关键设计: CPU 和 GPU 的 effective batch 保持 16 不变,学习率也统一为 5e-5。 这确保了训练动力学完全一致,消除了"CPU 能训出 0.94、GPU 只有 0.68"这种精度差异问题。

强制指定模式

device: cpu   # 即使有 GPU 也强制用 CPU(调试用)
device: cuda  # 强制使用 GPU(即使检测不到)

启动日志确认

每次运行你都会看到这段日志,一眼确认当前状态:

========================================================
设备检测: GPU
配置来源: auto
  no_cuda: false | fp16: true
  batch_size (train/eval): 8 / 8
  gradient_accumulation_steps: 2
========================================================

GPU 显存与手动调参参考

当自动值不满足需求时,可手动覆盖:

training:
  per_device_train_batch_size: 4      # 如果 8 爆显存,改回 4
  gradient_accumulation_steps: 4      # 显存不够时,用累积补

GPU 显存与批次大小速查(当 max_length=512 时):

GPU 型号 显存 安全 batch_size
RTX 3060 12GB 16
RTX 3090 / 4090 24GB 32
T4 (云) 16GB 16
A100 (云) 40/80GB 64

经验: 显存不够时优先减小 max_length 而非 batch_size。将 max_length 从 512 降到 256,显存占用减少约 40%,对分类任务的效果影响通常小于 0.5%。


TensorBoard 可视化

启动 TensorBoard

tensorboard --logdir ./output/bert-finetuned-intent-textclassify/logs

然后打开浏览器访问 http://localhost:6006

日志结构

Run 来源 内容
metrics_callback 训练过程中 MetricsCallback 实时写入 Training/loss(每个 logging step)、Evaluation/f1(每个 epoch)等
evaluation evaluate.py 评估时写入 Evaluation/f1_micro、Evaluation/accuracy、PerClass/f1/{类别名} 等
output/bert-finetuned-intent-textclassify/logs/
├── events.out.tfevents.*          ← ① Hugging Face Trainer 自带的日志
├── metrics_callback/
│   └── events.out.tfevents.*      ← ② MetricsCallback 写入的
└── evaluation/
    └── events.out.tfevents.*      ← ③ evaluate.py 写入的

核心优势: 训练曲线的 Evaluation/f1 和最终评估的 Evaluation/f1_micro 在同一个 TensorBoard 视图中并列展示,可以直观地看到模型在整个训练过程中验证集指标的变化趋势,以及最终在测试集上的表现。


一些个人模型训练经验总结

​ 模型训练在整个项目中仅占约20%的工作量,真正决定最终效果的核心是数据质量。许多新人倾向于研究不同预训练模型之间的性能差异,例如Bert、RoBERTa、MacBERT、DeBERTa哪个效果更好。然而在实际工业项目中,模型效果的影响因子排序如下:

数据质量 > 标签质量 > 数据分布 > Loss设计 > 模型结构

​ 若数据本身存在问题,再先进的模型也无法学习到有效的模式。值得注意的是,当数据体系稳定且标签边界清晰之后,采用更强的预训练模型或针对特定领域继续预训练,仍可带来显著提升——但前提是不能用更换模型来掩盖数据与标签层面的根本问题。

因此,整个工作思维模型遵循以下闭环流程:

业务问题
    ↓
标签设计 (Label)
    ↓
数据质量 (Data)
    ↓
模型训练 (Model)
    ↓
错误分析 (Error Analysis)
    ↓
重新优化数据

而非陷入“一直换模型”的误区。


十条经验总结

1. 永远先想分类边界,而不是模型

拿到一个新的文本分类任务时,第一件事不应是编写代码或搭建模型,而是向业务方提问:

  • 这几个类别究竟如何区分?

示例问题

某些公司初始设定的标签如下:

  • 设备报警
  • 设备故障
  • 设备异常
  • 设备停机

此类标签常导致Accuracy仅为75%,根本原因在于标签本身缺乏清晰的划分边界。

模型不具备人类的理解能力,无法判断“设备异常”是否属于“设备故障”。如果连人工标注员都难以分辨,则BERT更不可能学会。

经验结论

分类效果的上限实际上由标签设计(Label Design)决定。


2. 数据清洗比调参重要得多

工业数据最显著的特点是“脏”,例如同一语义的表达形式多样:

  • PLC通信失败!!
  • PLC通信失败!!!
  • PLC 通信失败
  • plc通信失败
  • PLC通信失败...

虽然对人眼而言这些表达一致,但Tokenizer会将其切分为不同的token序列(如大小写、空格、标点差异),从而导致Embedding发散。

推荐清洗步骤

  • Unicode Normalization
  • 全角半角统一
  • 大小写统一(视业务需求)
  • 空格处理
  • 特殊字符清洗
  • 重复符号压缩
  • HTML清理
  • 控制字符删除

实施上述清洗流程后,模型性能通常能直接提升数个百分点。

重要前提

不要无脑清洗。某些特殊符号、大小写或格式可能携带业务信号,例如:

  • “MAX_LEVEL”可能是系统固定常量
  • “!!”在某些场景下代表紧急程度

因此,清洗前必须先进行数据探索,确认哪些是噪声,哪些是有效信号。


3. 类别平衡比 Oversampling 更重要

许多教程推荐使用RandomOverSampler或SMOTE进行样本均衡,但在文本分类任务中,更推荐的做法是重新采集数据

原因分析

Oversampling本质上是对同一样本复制多次,BERT容易记住这些重复样本,但泛化能力不会提升。

真正有效的方法是让模型看到更多不同表达方式的同类样本,例如:

  • 设备启动失败
  • 启动异常
  • 启动过程中报错
  • 无法启动
  • 开机失败

尽管标签相同,但语言表达完全不同,这才是本质上的数据增强。

工业现实与替代方案

受限于采集成本,短期内难以获取足够真实样本时,可考虑以下文本数据增强方法:

  • 同义词替换
  • 回译(Back Translation)
  • 基于预训练模型的生成式增强

这些方法比简单复制样本更有效,也符合“增加表达多样性”的核心思想,但仍需警惕引入噪声。


4. 不要一开始就调模型

许多新手热衷于调整超参数,如:

  • learning_rate
  • batch_size
  • dropout
  • weight decay

并花费大量时间进行调参。

实际建议

当验证集Accuracy仅为60%时,基本不应立即陷入参数调整。这往往表明模型尚未进入“可学习”状态。

注意事项

有时极不合理的学习率或过大的batch size确实会导致模型完全无法收敛。更稳妥的做法是:

  • 使用一组成熟默认参数(如2e-5学习率、batch size=32)跑出基线
  • 在该基线上进行错误分析
  • 避免在严重欠拟合状态下强行归因

真正应优先执行的是:Error Analysis

通过分析预测错误的样本,明确问题所在。


5. Error Analysis 是整个项目最重要的环节

每次训练结束后,首要任务是导出所有预测错误的样本。

典型错误示例

  • 真实标签:设备停机
  • 预测标签:设备报警

随后深入分析原因,通常归结为以下四类:

  1. 数据标错
    人工标注本身就是错误的,这种情况非常普遍。

  2. 类别重叠
    如“报警”与“异常”之间边界不清,造成混淆。

  3. 样本太少
    某些类别样本稀少(如“液压报警”仅有30条),模型难以学习。

  4. 文本缺失上下文
    如“压力异常”未提供具体背景,任何模型都会猜测。

核心理念

每一次Error Analysis的本质都是在优化数据,而非优化模型。

典型闭环示例

发现“液压报警”样本仅30条,且与“液压故障”混淆严重 → 决定合并两个类别,或补采50条带上下文的新样本 → 重新训练后,该簇的Macro F1从0.45提升至0.71。这种迭代带来的收益远比更换模型更稳定可靠。


6. 不要迷信 Accuracy

在工业项目中,Accuracy常常具有欺骗性。

示例说明

  • 总数据量:10,000条
  • “正常”类占比:9,500条(95%)

此时模型若始终预测“正常”,Accuracy可达95%,但实际业务价值几乎为零。

推荐关注指标

  • Macro F1
  • Weighted F1
  • Precision
  • Recall
  • Confusion Matrix

其中,Confusion Matrix尤为关键,它能直观揭示哪些类别之间存在严重混淆。

易忽视陷阱

验证集和测试集的划分方式至关重要。若数据具有时间顺序,必须按时间切分,而非随机打乱,否则会导致时间泄露(Temporal Leakage),高估模型效果,上线后极易失败。


7. Learning Rate 不需要疯狂调

BERT微调的学习率,通常经验值已覆盖绝大多数场景:

推荐学习率
2e-5
3e-5
5e-5

真正影响效果的因素通常是数据是否干净,而非学习率是在2e-5还是3e-5之间微调。


8. Batch Size 不一定越大越好

普遍认为Batch Size越大训练越稳定,但实际上小Batch往往具备更好的泛化能力。

实践建议

  • 常用Batch Size:16、32、64
  • 若GPU显存不足,可使用Gradient Accumulation解决

该技术可在较小物理Batch Size下模拟较大Batch Size的梯度更新,兼顾训练稳定性与资源限制。


9. Early Stopping 是必须的

工业数据规模通常为几千至几十万条,BERT极易出现过拟合现象:

  • Train Loss持续下降
  • Validation Loss开始上升

实施策略

几乎所有项目都应启用EarlyStopping机制:

  • patience一般设为2~3
  • 若验证曲线波动较大(如小数据集),可放宽至5左右
  • 配合保存验证集上最佳checkpoint,有助于选出最优模型

10. 不要把所有希望寄托在模型上

工业NLP中最常见的误区是:

模型效果不好 → 换模型 → 效果仍差 → 继续换模型

正确路径

应采取如下闭环流程:

预测错误 → 分析原因 → 修改Label → 补充数据 → 重新训练

许多项目最终实现10个百分点的提升,并未更换任何模型,仅仅通过重新整理数据即达成目标。

进阶阶段建议

当数据和标签体系已较为稳固后,可进一步评估以下手段的收益:

  • 更强的预训练模型
  • 领域自适应预训练(如使用自有设备日志语料继续预训练BERT)

这一步骤通常能带来额外且稳定的性能提升。但关键顺序不可颠倒:先修数据,再谈模型


训练思维链(Thinking Pipeline)

每开展一个文本分类项目,脑海中遵循的流程基本固定如下:

  1. 业务需求分析
  2. 定义分类标签(Label Design)
  3. 抽样检查数据质量
  4. 数据清洗 + 标准化(先探索再清洗)
  5. 划分 Train / Validation / Test(注意时间维度,避免泄露)
  6. BERT Fine-tuning(Transformers,先用成熟默认参数建立基线)
  7. 模型评估(Macro F1、Precision、Recall、Confusion Matrix)
  8. Error Analysis(错误案例分析,归类为标错、重叠、稀疏、缺上下文)
  9. 修正标签 / 补充数据 / 优化类别边界 / 必要时使用合理的文本增强
  10. 重新训练
  11. (数据体系稳定后)评估更强预训练模型或领域继续预训练的收益

核心思想

模型训练不是一个一次性动作,而是一个持续迭代的数据闭环。在工业场景中,极少有团队通过频繁更换模型获得显著收益;更多时候,性能提升源于:

  • 更合理的标签体系
  • 更高质量的数据
  • 系统化的错误分析

最后的感悟

一句话:

机器学习不是让模型去理解数据,而是让数据去教会模型。

BERT、RoBERTa、MacBERT、DeBERTa等都只是学习器(Learner),真正决定模型上限的是数据本身。

工程实践中,与其花费一周时间尝试不同Backbone,不如将时间用于:

  • 高质量的数据清洗
  • 标签体系复审
  • 系统性错误分析

同样一套模型代码,针对不用的数据集 效果不一样,why?

1.数据量

2.数据质量

3.数据标签分类均衡度

**Point: **

Bert 模型是深度模型,深度模型是数驱模型,需要数据进行驱动,同样数据质量情况下,数据量越大,模型效果要想好的效果越好。个人建议 数据最好超过1w,因为学习的数据太少,效果提不上来。

备注:我用的几千条样本数据训练 模型一直卡在0.70准确率上不去,数据增加到1万多条,效果一下子蹦到了0.96。 很讽刺对吧。

经验总结

业务定义决定问题边界,数据质量决定模型上限,

模型训练只是把数据中的规律拟合出来,而Error Analysis才是连接模型优化与数据迭代的核心闭环。

License

MIT

About

基于 BERT 的工业级文本分类项目。使用 Hugging Face `transformers` 的 Trainer API 进行训练, 支持完整的训练 → 评估 → 推理流程,并通过 TensorBoard 统一记录训练过程和评估指标。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages