AniFileBERT / diagnostics_report.md
ModerRAS's picture
Add parser diagnostics and inference debugging
b57780c
|
raw
history blame
11.4 kB
# Anime Filename Parser Diagnostics Report
## 根因分析
当前症状不是 learning rate 问题,而是训练、验证、推理没有在同一个结构化输入空间里工作。
最高优先级根因是 tokenizer/data 配置错位:你给出的训练命令使用 `dmhy_weak_char.jsonl``vocab.char.json`,但没有传 `--tokenizer char`。旧版 `train.py` 默认 `regex`,因此 char 数据会被当作 regex 训练配置保存,checkpoint metadata 会写成 `tokenizer_variant=regex`。推理时 `load_tokenizer()` 按 checkpoint metadata 重新加载 regex tokenizer,于是 `[LoliHouse]` 这类结构 token 会作为一个整体进入模型,而 char 训练数据里它是 `[`, `L`, `o`, ..., `]`。这会直接导致 group/title 边界漂移。
第二个根因是 word-level 数据和当前 `AnimeTokenizer` 也不完全一致。`dmhy_weak.jsonl` 里示例 token 是 `[`, `LoliHouse`, `]`,但当前 regex tokenizer 对原始文件名会输出 `[LoliHouse]`。这说明 word-level 数据名义上是 regex,但不是严格由当前 inference tokenizer 重放得到的 token 序列。
第三个根因是 char 训练命令没有设置 `--max-seq-length 128`。在抽样 5,000 条 char 数据中,默认 64 长度会截断 2,058 条,占 41.16%。episode/source/resolution 往往在后半段,默认长度会让模型训练和推理都丢失结构锚点。
第四个根因是评估指标误导。低 validation loss 和 token accuracy 会被大量 `O``I-TITLE` 稀释;真实任务需要 entity-level F1、字段 exact match,以及结构案例回归。
## 问题优先级
P0: 训练命令必须显式或自动使用 char tokenizer。已修改 `train.py`,现在会从数据集 metadata 自动识别 `char`,并把 char 默认 max length 提升到 128。
P0: 不允许 tokenizer variant 与 dataset metadata 不一致。已修改 `train.py`,检测到 dataset `tokenizer_variant` 与选择的 tokenizer 不一致会报错。
P0: 推理必须使用 checkpoint 保存的 tokenizer 和 max length。已修改 `inference.py`,默认读取 `model.config.max_seq_length`,并新增 `--debug` 输出 token/label/score/UNK/截断信息。
P1: 从旧 checkpoint fine-tune 到不同 vocab 时,不能按 ID 盲目 `resize_token_embeddings()`。已修改为按 token 字符串重映射 embedding,未匹配 token 再随机初始化。
P1: 数据集存在 BIO/边界质量问题。char 抽样 5,000 条发现 468 个 `ORPHAN_I`,典型是标题被括号 `O` 打断后仍继续 `I-TITLE``B-X -> O` 本身是合法 BIO,但在 group/title/source 频繁出现时是边界告警。
P2: 当前 `BertForTokenClassification` 独立逐 token 解码,不能约束非法转移。建议后续加 CRF 或 constrained BIO decoder。
## 自动诊断结果
新增脚本:
```bash
python diagnose_pipeline.py --data-file datasets/AnimeName/dmhy_weak_char.jsonl --vocab-file datasets/AnimeName/vocab.char.json --model-dir checkpoints/dmhy-finetune/final --sample-limit 5000 --eval-limit 128 --output diagnostics_report.md
```
char 数据抽样结果:
- tokenizer variant: `char`
- vocab size: 6,199
- UNK rate: 0.0000%
- O-label ratio: 37.47%
- p95 length: 101, p99 length: 125
- default max length 64 truncation: 41.16%
- `ORPHAN_I`: 468
- regex checkpoint 直接评 char 数据时 entity F1: 0.0832
word 数据抽样结果保存在 `diagnostics_report_word.md`
- tokenizer variant: `regex`
- vocab size: 8,000
- UNK rate: 6.9158%
- default max length 64 truncation: 0%
- 当前 regex checkpoint 在抽样 word 数据上 entity F1: 0.9549
- 但 model checkpoint vocab 是 3,000,诊断 vocab 是 8,000,继续 fine-tune 必须重映射 embedding
## Tokenizer Split 示例
输入:
```text
[LoliHouse] Yomi no Tsugai - 07 [WebRip 1080p HEVC-10bit AAC ASSx2]
```
char tokenizer:
```text
[, L, o, l, i, H, o, u, s, e, ], , Y, o, m, i, , n, o, , T, s, u, g, a, i, , -, , 0, 7, ...
```
当前 regex tokenizer:
```text
[LoliHouse], , Yomi, , no, , Tsugai, , -, , 07, , [WebRip 1080p HEVC-10bit AAC ASSx2]
```
这两个 token 序列不是同一个标注空间。char label 不能直接套到 regex token 上,regex 模型也不能在 char token 序列上解释 logits。
## BIO 与边界问题
真实非法 BIO:
```text
... ( O, K I-TITLE, a I-TITLE ...
```
示例:
```text
[LoliHouse] Kanteishi (Kari) - 07 [WebRip 1080p HEVC-10bit AAC]
```
`(` 被标为 `O`,后面的 `Kari` 继续 `I-TITLE`,形成 `O -> I-TITLE`。这会让模型学习到标题可以跨越被标为非实体的括号,边界自然会漂。
结构边界告警:
```text
[KissSub][Shunkashuutou Daikousha - Haru no Mai][06][1080P][GB][MP4]
```
`KissSub``B-GROUP`,右括号是 `O`,这是合法 BIO;但如果 tokenizer 在推理时把 `[KissSub]` 合成一个 token,模型就无法只给内部文字打 `GROUP`,只能把整个 bracket token 判成一个类别。
## Confusion 分析
故意用 char 数据评估 regex checkpoint,entity F1 只有 0.0832。主要混淆:
- `O -> TITLE`: 930
- `SOURCE -> TITLE`: 236
- `EPISODE -> TITLE`: 228
- `GROUP -> TITLE`: 86
这与实际症状一致:模型把结构锚点和 meta 区域吸进 title,group/title 边界混淆,episode 被 title 或 O 吞掉。
## 已修改的代码
`train.py`
- `--tokenizer` 默认从数据集 metadata/vocab 名称/样本结构自动推断。
- char 数据默认 `max_seq_length >= 128`
- dataset metadata 与 tokenizer 不一致会直接报错。
- fine-tune 到新 vocab 时按 token 字符串重映射 embedding,避免 token ID 语义错位。
- checkpoint 保存正确的 `tokenizer_variant``max_seq_length`
`inference.py`
- 新增 `--debug`,输出 tokenizer variant、token IDs、labels、scores、UNK rate、truncation、entity spans。
- 默认使用 checkpoint `max_seq_length`
- 修正推理截断逻辑,保留 `[SEP]`,与训练一致。
- 默认使用 constrained BIO Viterbi 解码,阻止 `O -> I-X` 这类非法转移;可用 `--no-constrained-bio` 查看原始 greedy 输出。
- 新增 rule-assisted parsing,兜底修复高置信结构锚点:leading group bracket、` - 07``S01E07`、resolution、source。
- 可用 `--no-rule-assist` 关闭规则兜底,只看模型原始输出。
`diagnose_pipeline.py`
- 自动检查 token/label 长度。
- 输出 BIO 违规样本与边界告警。
- 输出 tokenizer split 示例。
- 输出 train/inference tokenizer 对比。
- 输出实体、label、空格 label、UNK、截断统计。
- 可选加载 checkpoint 做 confusion 和 seqeval entity-level F1。
## 修改后的 Pipeline
推荐 char-level pipeline:
```bash
python diagnose_pipeline.py ^
--data-file datasets/AnimeName/dmhy_weak_char.jsonl ^
--vocab-file datasets/AnimeName/vocab.char.json ^
--sample-limit 20000 ^
--output diagnostics_report.md
python train.py ^
--tokenizer char ^
--data-file datasets/AnimeName/dmhy_weak_char.jsonl ^
--vocab-file datasets/AnimeName/vocab.char.json ^
--save-dir checkpoints/dmhy-char ^
--epochs 10 ^
--batch-size 128 ^
--learning-rate 0.0003 ^
--warmup-steps 300 ^
--max-seq-length 128 ^
--seed 42
python inference.py ^
--model-dir checkpoints/dmhy-char/final ^
--debug ^
"[LoliHouse] Yomi no Tsugai - 07 [WebRip 1080p HEVC-10bit AAC ASSx2]"
```
如果继续使用 word/regex pipeline,必须先重新生成数据,使 `sample["tokens"] == AnimeTokenizer.tokenize(sample["filename"])` 对绝大多数样本成立;否则验证集仍然是训练 token 空间,真实 inference 是另一个 token 空间。
## 最合理的 Tokenizer 方案
当前任务更适合 char-level 或 deterministic hybrid tokenizer,不适合通用 subword tokenizer。
char-level 优点:
- train/inference 最容易完全一致。
- 不会把 `[LoliHouse]``[WebRip ...]` 这类结构块压成单 token。
- 对未知标题、组名、罗马音、中文、日文都没有 OOV。
- 更适合学习括号、空格、连字符、集数位置这些结构信号。
char-level 缺点:
- 序列更长,必须用 `max_seq_length=128`
- 逐 token softmax 容易出现 BIO 非法转移,建议加 CRF。
word-level/regex 优点:
- 序列短,训练快。
- 当前已有 checkpoint 在同 token 空间验证集上 F1 较高。
word-level/regex 缺点:
- 如果 bracket protection 把整段合并,内部 label 无法表达。
- 数据生成 tokenizer 和 inference tokenizer 稍有不一致就会严重错位。
- OOV 对新番标题和组名仍然明显。
结论:短期用 char-level + rule-assisted parsing;中期改为 hybrid tokenizer:保留结构符号 `[ ] ( ) - _ . space` 为独立 token,英文数字连续串可作为片段但必须能映射回字符 offset,并在 label alignment 上以 offset 为准;长期加 BERT + CRF。
## 建议训练配置
首选:
```bash
python train.py --tokenizer char ^
--data-file datasets/AnimeName/dmhy_weak_char.jsonl ^
--vocab-file datasets/AnimeName/vocab.char.json ^
--save-dir checkpoints/dmhy-char ^
--epochs 10 --batch-size 128 ^
--learning-rate 0.0003 --warmup-steps 300 ^
--max-seq-length 128 --seed 42
```
不要从 regex checkpoint 直接当作同构模型继续训练 char;如果要迁移,当前代码会按 token 字符串 remap embedding,但多数 char token 与 regex token 共享有限,最好从头训练 char 模型或只迁移 encoder 非 embedding 层。
必须新增评估:
- entity-level F1 by field
- field exact match: `group/title/episode/resolution/source`
- full parse exact match
- episode recall
- boundary errors: group-title, title-episode, episode-meta
- inference debug sample set,固定 50-200 个真实文件名回归
## 真实案例分析
输入:
```text
[LoliHouse] Yomi no Tsugai - 07 [WebRip 1080p HEVC-10bit AAC ASSx2]
```
旧 regex checkpoint 原始模型输出:
```json
{
"entities": [
{"type": "TITLE", "text": "[LoliHouse] Yomi no Tsugai"},
{"type": "EPISODE", "text": "07"}
]
}
```
问题点:
- `[LoliHouse]` 被 tokenizer 合成一个 token。
- 模型把该 token 判成 `B-TITLE`,无法只把内部 `LoliHouse` 判成 `GROUP`
- `Yomi``Tsugai` 在 3,000 vocab checkpoint 中是 `[UNK]`,但模型仍高置信输出 `I-TITLE`,说明 loss/置信度不能代表字段正确性。
修改后带规则辅助的最终输出:
```json
{
"group": "LoliHouse",
"title": "Yomi no Tsugai",
"episode": 7,
"source": "WebRip",
"resolution": "1080p"
}
```
这只是上线兜底;真正修复仍应训练一个 train/inference token 完全一致的 char 或 hybrid 模型。
## 架构建议
最推荐的重构路线:
1. `BERT encoder + CRF`:约束 `O -> I-X``B-X -> I-Y` 等非法/低质量转移。
2. char-level NER:保证 token-label alignment 不受 subword split 影响。
3. rule-assisted parser:先抽取高置信结构锚点,再让模型负责模糊 title/group 边界。
4. offset-based dataset:每条数据保存 raw filename、entity spans、tokens、offset_mapping、labels,训练时由 tokenizer 统一生成 labels。
当前代码已先实现“无训练 CRF”的 constrained BIO decoding,作为上线前的轻量保护。完整 BERT+CRF 仍建议作为下一阶段训练架构重构。
不要只优化 loss。这个任务的目标函数应更接近真实解析准确率:字段级 exact match + episode recall + title boundary F1。