基于 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 # 项目说明
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.yaml中model.pretrained_model_name_or_path为你的路径,如/opt/work/bert-base-chinese。
python train.py
# 或指定配置文件 + 从 checkpoint 恢复
python train.py --config ./config/config.yaml --resume checkpoint-500训练过程中会自动:
- 从
train.csv按 80% / 10% / 10% 划分训练集、验证集、测试集(划分索引缓存到文件,跨平台可复现) - 自动扫描数据中的所有标签,生成
label2id/id2label映射 - 自动检测 CPU / GPU,调整 batch_size、fp16、梯度累积步数
- 每个 epoch 在验证集上评估,以 F1 为指标保存最佳模型
- 训练和评估指标通过
MetricsCallback实时写入 TensorBoard - 训练结束后在测试集上做最终评估并输出结果
- 最佳模型和分词器保存至
./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', ...]
python evaluate.py
# 或指定模型路径
python evaluate.py --model_path ./output/bert-finetuned-intent-textclassify/best_model评估脚本会:
- 加载训练好的最佳模型
- 在测试集上进行预测
- 计算多维度指标(F1 micro / macro、准确率、精确率、召回率、各类别 F1)
- 将最终评估指标写入 TensorBoard(与训练曲线并列显示)
- 输出详细的分类报告和混淆矩阵
# 单条文本预测
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
...
pytest tests/ -vdevice: 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
========================================================
当自动值不满足需求时,可手动覆盖:
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 --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)
↓
重新优化数据
而非陷入“一直换模型”的误区。
拿到一个新的文本分类任务时,第一件事不应是编写代码或搭建模型,而是向业务方提问:
- 这几个类别究竟如何区分?
某些公司初始设定的标签如下:
- 设备报警
- 设备故障
- 设备异常
- 设备停机
此类标签常导致Accuracy仅为75%,根本原因在于标签本身缺乏清晰的划分边界。
模型不具备人类的理解能力,无法判断“设备异常”是否属于“设备故障”。如果连人工标注员都难以分辨,则BERT更不可能学会。
分类效果的上限实际上由标签设计(Label Design)决定。
工业数据最显著的特点是“脏”,例如同一语义的表达形式多样:
- PLC通信失败!!
- PLC通信失败!!!
- PLC 通信失败
- plc通信失败
- PLC通信失败...
虽然对人眼而言这些表达一致,但Tokenizer会将其切分为不同的token序列(如大小写、空格、标点差异),从而导致Embedding发散。
- Unicode Normalization
- 全角半角统一
- 大小写统一(视业务需求)
- 空格处理
- 特殊字符清洗
- 重复符号压缩
- HTML清理
- 控制字符删除
实施上述清洗流程后,模型性能通常能直接提升数个百分点。
不要无脑清洗。某些特殊符号、大小写或格式可能携带业务信号,例如:
- “MAX_LEVEL”可能是系统固定常量
- “!!”在某些场景下代表紧急程度
因此,清洗前必须先进行数据探索,确认哪些是噪声,哪些是有效信号。
许多教程推荐使用RandomOverSampler或SMOTE进行样本均衡,但在文本分类任务中,更推荐的做法是重新采集数据。
Oversampling本质上是对同一样本复制多次,BERT容易记住这些重复样本,但泛化能力不会提升。
真正有效的方法是让模型看到更多不同表达方式的同类样本,例如:
- 设备启动失败
- 启动异常
- 启动过程中报错
- 无法启动
- 开机失败
尽管标签相同,但语言表达完全不同,这才是本质上的数据增强。
受限于采集成本,短期内难以获取足够真实样本时,可考虑以下文本数据增强方法:
- 同义词替换
- 回译(Back Translation)
- 基于预训练模型的生成式增强
这些方法比简单复制样本更有效,也符合“增加表达多样性”的核心思想,但仍需警惕引入噪声。
许多新手热衷于调整超参数,如:
- learning_rate
- batch_size
- dropout
- weight decay
并花费大量时间进行调参。
当验证集Accuracy仅为60%时,基本不应立即陷入参数调整。这往往表明模型尚未进入“可学习”状态。
有时极不合理的学习率或过大的batch size确实会导致模型完全无法收敛。更稳妥的做法是:
- 使用一组成熟默认参数(如2e-5学习率、batch size=32)跑出基线
- 在该基线上进行错误分析
- 避免在严重欠拟合状态下强行归因
真正应优先执行的是:Error Analysis
通过分析预测错误的样本,明确问题所在。
每次训练结束后,首要任务是导出所有预测错误的样本。
- 真实标签:设备停机
- 预测标签:设备报警
随后深入分析原因,通常归结为以下四类:
-
数据标错
人工标注本身就是错误的,这种情况非常普遍。 -
类别重叠
如“报警”与“异常”之间边界不清,造成混淆。 -
样本太少
某些类别样本稀少(如“液压报警”仅有30条),模型难以学习。 -
文本缺失上下文
如“压力异常”未提供具体背景,任何模型都会猜测。
每一次Error Analysis的本质都是在优化数据,而非优化模型。
发现“液压报警”样本仅30条,且与“液压故障”混淆严重 → 决定合并两个类别,或补采50条带上下文的新样本 → 重新训练后,该簇的Macro F1从0.45提升至0.71。这种迭代带来的收益远比更换模型更稳定可靠。
在工业项目中,Accuracy常常具有欺骗性。
- 总数据量:10,000条
- “正常”类占比:9,500条(95%)
此时模型若始终预测“正常”,Accuracy可达95%,但实际业务价值几乎为零。
- Macro F1
- Weighted F1
- Precision
- Recall
- Confusion Matrix
其中,Confusion Matrix尤为关键,它能直观揭示哪些类别之间存在严重混淆。
验证集和测试集的划分方式至关重要。若数据具有时间顺序,必须按时间切分,而非随机打乱,否则会导致时间泄露(Temporal Leakage),高估模型效果,上线后极易失败。
BERT微调的学习率,通常经验值已覆盖绝大多数场景:
| 推荐学习率 |
|---|
| 2e-5 |
| 3e-5 |
| 5e-5 |
真正影响效果的因素通常是数据是否干净,而非学习率是在2e-5还是3e-5之间微调。
普遍认为Batch Size越大训练越稳定,但实际上小Batch往往具备更好的泛化能力。
- 常用Batch Size:16、32、64
- 若GPU显存不足,可使用Gradient Accumulation解决
该技术可在较小物理Batch Size下模拟较大Batch Size的梯度更新,兼顾训练稳定性与资源限制。
工业数据规模通常为几千至几十万条,BERT极易出现过拟合现象:
- Train Loss持续下降
- Validation Loss开始上升
几乎所有项目都应启用EarlyStopping机制:
- patience一般设为2~3
- 若验证曲线波动较大(如小数据集),可放宽至5左右
- 配合保存验证集上最佳checkpoint,有助于选出最优模型
工业NLP中最常见的误区是:
模型效果不好 → 换模型 → 效果仍差 → 继续换模型
应采取如下闭环流程:
预测错误 → 分析原因 → 修改Label → 补充数据 → 重新训练
许多项目最终实现10个百分点的提升,并未更换任何模型,仅仅通过重新整理数据即达成目标。
当数据和标签体系已较为稳固后,可进一步评估以下手段的收益:
- 更强的预训练模型
- 领域自适应预训练(如使用自有设备日志语料继续预训练BERT)
这一步骤通常能带来额外且稳定的性能提升。但关键顺序不可颠倒:先修数据,再谈模型。
每开展一个文本分类项目,脑海中遵循的流程基本固定如下:
- 业务需求分析
- 定义分类标签(Label Design)
- 抽样检查数据质量
- 数据清洗 + 标准化(先探索再清洗)
- 划分 Train / Validation / Test(注意时间维度,避免泄露)
- BERT Fine-tuning(Transformers,先用成熟默认参数建立基线)
- 模型评估(Macro F1、Precision、Recall、Confusion Matrix)
- Error Analysis(错误案例分析,归类为标错、重叠、稀疏、缺上下文)
- 修正标签 / 补充数据 / 优化类别边界 / 必要时使用合理的文本增强
- 重新训练
- (数据体系稳定后)评估更强预训练模型或领域继续预训练的收益
模型训练不是一个一次性动作,而是一个持续迭代的数据闭环。在工业场景中,极少有团队通过频繁更换模型获得显著收益;更多时候,性能提升源于:
- 更合理的标签体系
- 更高质量的数据
- 系统化的错误分析
一句话:
机器学习不是让模型去理解数据,而是让数据去教会模型。
BERT、RoBERTa、MacBERT、DeBERTa等都只是学习器(Learner),真正决定模型上限的是数据本身。
工程实践中,与其花费一周时间尝试不同Backbone,不如将时间用于:
- 高质量的数据清洗
- 标签体系复审
- 系统性错误分析
同样一套模型代码,针对不用的数据集 效果不一样,why?
1.数据量
2.数据质量
3.数据标签分类均衡度
**Point: **
Bert 模型是深度模型,深度模型是数驱模型,需要数据进行驱动,同样数据质量情况下,数据量越大,模型效果要想好的效果越好。个人建议 数据最好超过1w,因为学习的数据太少,效果提不上来。
备注:我用的几千条样本数据训练 模型一直卡在0.70准确率上不去,数据增加到1万多条,效果一下子蹦到了0.96。 很讽刺对吧。
业务定义决定问题边界,数据质量决定模型上限,
模型训练只是把数据中的规律拟合出来,而Error Analysis才是连接模型优化与数据迭代的核心闭环。

