chivehao
/

File size: 11,417 Bytes
f7b1036
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# 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。