BERT分类模型训练数据准备:从知识条目到问法变体

📌 关键要点

本文记录 HXXedge 系统 BERT 分类模型的完整训练数据准备流程,以及在实际部署中遇到的类别重复问题。


数据准备流程

整体架构

knowledge/*.md(原始知识文件)
    ↓ extract_knowledge_entries.py(DeepSeek 提取)
preset_qa.json(结构化 QA 条目)
    ↓ generate_training_data.py(DeepSeek 生成变体)
train.jsonl / val.jsonl(训练数据)
    ↓ Colab 训练
ONNX 模型 → categories.json(推理时答案源)

关键文件说明

文件 作用 备注
preset_qa.json 权威数据源 所有 Q&A 条目的源头,带版本号
generate_training_data.py 训练数据生成器 读取 preset_qa.json,生成问法变体
train.jsonl 训练集 约 80% 数据
val.jsonl 验证集 约 20% 数据
categories.json 推理时答案源 模型输出 label → 查答案

preset_qa.json 结构

每个知识条目包含以下字段:

{
  "id": 30,
  "category": "表层海水温度",
  "keywords": ["表层海水温度", "海水温度", "海温", "SST"],
  "questions_std": [
    "表层海水温度是什么",
    "什么是表层海水温度",
    "海温",
    "海水温度"
  ],
  "answer": "表层海水温度(又称海表面温度、SST)是指海表面0.5米以内水层的温度...",
  "enabled": true,
  "source_file": "海洋观测规范 第2部分:海滨观测.md"
}

字段说明:

字段 用途 推理时是否使用
id 类别编号(0-indexed) ✅ 模型输出
category 类别名称 ✅ 返回给前端
keywords 用于训练数据生成时的参考 ❌ 不进入训练
questions_std 标准问法,直接进入训练数据 ✅ 模型学习
answer 预设答案 ✅ 查表返回
source_file 来源追溯 ❌ 仅记录

问法变体生成机制

生成策略

generate_training_data.py 为每个类别生成 50 条 DeepSeek 变体:

QUESTIONS_PER_CATEGORY = 50

实际训练数据量 = questions_std + 50 条变体

例如"表层海水温度"类别: - questions_std: 14 条(标准问法 + 简短问法) - DeepSeek 变体: 50 条 - 合计: ~64 条

为什么需要变体?

BERT 是语义分类模型,需要学习各种问法:

原始问法 变体示例
表层海水温度是什么 海温多少度、海水表面温度指啥、SST是啥
台风怎么形成 台风是怎么来的、形成台风的条件、台风生成过程

questions_std 的重要性

v3 版本修复:原先 questions_std 只作为 DeepSeek 生成变体的示例,不直接进入训练数据。这导致简短问法(如"海温")可能丢失。

修复后,questions_std 直接进入训练数据:

# 先加入标准问法(questions_std),确保简短问法不丢失
for question in questions_std:
    all_data.append({"text": question, "label": label, "category": category})

# 再加入 DeepSeek 生成的变体
for question in variations:
    all_data.append({"text": question, "label": label, "category": category})

踩坑记录:类别重复导致短问题分类失败

问题现象

部署 v3 模型后,测试发现:

问题 预期 实际
海温 回答"表层海水温度" ❌ 拒答
海水温度 回答"表层海水温度" ❌ 拒答
海表温度 回答"表层海水温度" ❌ 拒答

诊断过程

Step 1:检查训练数据

确认"海温"在 train.jsonl 中:

{"text": "海温", "label": 29, "category": "表层海水温度"}

Step 2:添加 DEBUG 输出

在 bert_classifier.py 添加调试日志:

print(f"[DEBUG] 问题: '{question}' → 类别: {category_info['category']}(label={pred_label}), 置信度: {confidence:.4f}")

Step 3:发现真相

[DEBUG] 问题: '海温' → 类别: 海表面温度(label=61), 置信度: 0.4985, 阈值: 0.5, 可靠: False

模型把"海温"分类为 label 61(海表面温度),而不是 label 29(表层海水温度)!

根因分析

检查 categories.json,发现两个类别语义重复:

Label 类别 答案 来源
29 表层海水温度 海水表面到0.5m...准确度分三级... 海洋观测规范
61 海表面温度 海表面0.5米以内...又称表层海温... 海洋预报术语

keywords 也重叠: - Label 29: "海温", "海水温度", "表层水温" - Label 61: "海温", "表层海温", "海表温度", "SST"

同一概念出现在两份不同标准文档中,提取脚本各自提取,没有做跨文件去重。

解决方案

合并重复类别:

  1. 将 label 61 的 keywords 合并到 label 29
  2. 合并两者的答案
  3. 删除 label 61
  4. 重编号 62-82 → 61-81
  5. 83 类 → 82 类

合并后的答案:

表层海水温度(又称海表面温度、SST)是指海表面0.5米以内水层的温度。它主要取决于太阳辐射和大洋环流两个因子。单位为摄氏度(℃),准确度分为三级:一级±0.05℃,二级±0.2℃,三级±0.5℃。

版本管理

版本 类别数 说明
v1 17 初始版本
v2 83 从知识文件提取
v3 82 合并重复类别(表层海水温度/海表面温度)

目录结构:

training_data/
├── preset_qa.json      ← 工作副本(当前 v3,82类)
├── v1/                 ← 历史快照(17类)
├── v2/                 ← 历史快照(83类)
└── v3/                 ← 当前训练数据(82类)

经验教训

预防措施

新增知识条目前,必须检查 preset_qa.json 是否已有语义相近的类别:

  1. 对比 keywords:如果新条目的 keywords 与现有条目重叠率 > 50%,应合并而非新增
  2. 检查短问题:训练完成后,用 DEBUG 输出检查短问题(2-4字)的置信度,低于 0.60 的需排查
  3. 版本追溯:preset_qa.json 带版本号,每个训练版本保留快照用于追溯

置信度阈值调整

阈值 适用场景
0.70 原始设置,较严格
0.60 推荐值,平衡准确率和召回率
0.50 临时调试用
0.40 当前设置(用户自定义)

合并重复类别后,"海温"置信度已提升至 94.16%,阈值设置更加灵活。


训练结果与验证

v3 模型训练

指标 数值
类别数 82
训练集 3610 条
验证集 903 条
验证准确率 96.79%
训练轮数 5
批大小 32
学习率 5e-05

"海温"问题修复效果

问题 修复前 修复后
海温 ❌ 拒答(置信度 49.85%) ✅ 回答正确(置信度 94.16%)
海表温度 ❌ 拒答 ✅ 回答正确(置信度 94.57%)
海水温度 ❌ 拒答 ✅ 回答正确(置信度 92.31%)

置信度提升:49.85% → 94.16%(+44.31%)


相关文件

文件 路径
preset_qa.json training_data/preset_qa.json
generate_training_data.py training_data/generate_training_data.py
bert_classifier.py HXXedge/bert_classifier.py
config.py HXXedge/config.py
v3 训练数据 training_data/v3/
ONNX 模型 hqx-bert-base-cs_onnx_v3/

📝 创建:2026-09-12 | 更新:2026-09-12(添加训练结果) | 标签:BERT、训练数据、HXXedge、分类模型

相关笔记