📌 关键要点
本文记录 HXXedge 系统 BERT 分类模型的完整训练数据准备流程,以及在实际部署中遇到的类别重复问题。
- 数据链路:preset_qa.json → generate_training_data.py → train.jsonl/val.jsonl → Colab 训练 → ONNX 模型
- 问法变体:每个类别通过 DeepSeek 生成 50 条问法变体,确保模型能识别各种问法
- 踩坑记录:类别语义重复导致短问题(如"海温")分类置信度低,最终通过合并重复类别解决
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 → 查答案 |
每个知识条目包含以下字段:
{
"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是啥 |
| 台风怎么形成 | 台风是怎么来的、形成台风的条件、台风生成过程 |
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"
同一概念出现在两份不同标准文档中,提取脚本各自提取,没有做跨文件去重。
合并重复类别:
合并后的答案:
表层海水温度(又称海表面温度、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 是否已有语义相近的类别:
| 阈值 | 适用场景 |
|---|---|
| 0.70 | 原始设置,较严格 |
| 0.60 | 推荐值,平衡准确率和召回率 |
| 0.50 | 临时调试用 |
| 0.40 | 当前设置(用户自定义) |
合并重复类别后,"海温"置信度已提升至 94.16%,阈值设置更加灵活。
| 指标 | 数值 |
|---|---|
| 类别数 | 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、分类模型