# 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。