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.jsonlvocab.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 会被大量 OI-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-TITLEB-X -> O 本身是合法 BIO,但在 group/title/source 频繁出现时是边界告警。

P2: 当前 BertForTokenClassification 独立逐 token 解码,不能约束非法转移。建议后续加 CRF 或 constrained BIO decoder。

自动诊断结果

新增脚本:

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 示例

输入:

[LoliHouse] Yomi no Tsugai - 07 [WebRip 1080p HEVC-10bit AAC ASSx2]

char tokenizer:

[, 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:

[LoliHouse],  , Yomi,  , no,  , Tsugai,  , -,  , 07,  , [WebRip 1080p HEVC-10bit AAC ASSx2]

这两个 token 序列不是同一个标注空间。char label 不能直接套到 regex token 上,regex 模型也不能在 char token 序列上解释 logits。

BIO 与边界问题

真实非法 BIO:

... ( O, K I-TITLE, a I-TITLE ...

示例:

[LoliHouse] Kanteishi (Kari) - 07 [WebRip 1080p HEVC-10bit AAC]

( 被标为 O,后面的 Kari 继续 I-TITLE,形成 O -> I-TITLE。这会让模型学习到标题可以跨越被标为非实体的括号,边界自然会漂。

结构边界告警:

[KissSub][Shunkashuutou Daikousha - Haru no Mai][06][1080P][GB][MP4]

KissSubB-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_variantmax_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、 - 07S01E07、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:

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。

建议训练配置

首选:

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 个真实文件名回归

真实案例分析

输入:

[LoliHouse] Yomi no Tsugai - 07 [WebRip 1080p HEVC-10bit AAC ASSx2]

旧 regex checkpoint 原始模型输出:

{
  "entities": [
    {"type": "TITLE", "text": "[LoliHouse] Yomi no Tsugai"},
    {"type": "EPISODE", "text": "07"}
  ]
}

问题点:

  • [LoliHouse] 被 tokenizer 合成一个 token。
  • 模型把该 token 判成 B-TITLE,无法只把内部 LoliHouse 判成 GROUP
  • YomiTsugai 在 3,000 vocab checkpoint 中是 [UNK],但模型仍高置信输出 I-TITLE,说明 loss/置信度不能代表字段正确性。

修改后带规则辅助的最终输出:

{
  "group": "LoliHouse",
  "title": "Yomi no Tsugai",
  "episode": 7,
  "source": "WebRip",
  "resolution": "1080p"
}

这只是上线兜底;真正修复仍应训练一个 train/inference token 完全一致的 char 或 hybrid 模型。

架构建议

最推荐的重构路线:

  1. BERT encoder + CRF:约束 O -> I-XB-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。