ModerRAS commited on
Commit
b7f5465
·
1 Parent(s): f2c565a

Simplify title-boundary title span heuristic

Browse files
AGENTS.md CHANGED
@@ -134,16 +134,18 @@ cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml --
134
  ```
135
 
136
  For reviewer calibration, run stratified samples before full review. Use
137
- `--title-boundary-heuristic-contains` to inspect high-similar duplicate/alias
138
- keeps, structural keeps, and path-boundary keeps that are normally skipped by
139
- the ambiguous merge review:
 
 
140
 
141
  ```powershell
142
  cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml -- `
143
  --review-title-boundaries-pi-rpc `
144
- --recipes reports\dmhy_template_recipes.full_top5000.title_boundary_trial.seed.jsonl `
145
- --title-boundary-decisions-output reports\dmhy_title_boundary_pi_rpc_highsim_keep_sample.jsonl `
146
- --title-boundary-heuristic-contains keep_similar_duplicate_or_alias `
147
  --title-boundary-min-similarity 0.85 `
148
  --title-boundary-max-similarity 1.0 `
149
  --threads 4 `
 
134
  ```
135
 
136
  For reviewer calibration, run stratified samples before full review. Use
137
+ `--title-boundary-heuristic-contains` to inspect high-similarity adjacent-title
138
+ merges, structural keeps, and path-boundary keeps that are normally skipped by
139
+ the ambiguous merge review. The old high-similarity duplicate/alias keep
140
+ heuristic was removed after Pi RPC review found it over-preserved title
141
+ continuations:
142
 
143
  ```powershell
144
  cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml -- `
145
  --review-title-boundaries-pi-rpc `
146
+ --recipes reports\dmhy_template_recipes.full_top5000.seed.jsonl `
147
+ --title-boundary-decisions-output reports\dmhy_title_boundary_pi_rpc_adjacent_highsim_sample.jsonl `
148
+ --title-boundary-heuristic-contains merge_adjacent_title_continuation `
149
  --title-boundary-min-similarity 0.85 `
150
  --title-boundary-max-similarity 1.0 `
151
  --threads 4 `
docs/title_boundary_heuristic_experiments.md ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Title-Boundary Heuristic Experiments
2
+
3
+ This note records deterministic Rust heuristic changes and Pi RPC reviewer evidence
4
+ for DMHY template `title_spans` generation. The reviewer output is evidence only;
5
+ recipes and training JSONL are changed only by Rust code.
6
+
7
+ ## Baseline
8
+
9
+ Input recipe evidence:
10
+
11
+ - `reports/dmhy_template_recipes.full_top5000.title_boundary_trial.seed.jsonl`
12
+ - total title-boundary decisions: `5631`
13
+
14
+ Decision distribution:
15
+
16
+ | decision | count |
17
+ | --- | ---: |
18
+ | `merge_dissimilar_title_continuation` | 4661 |
19
+ | `keep_similar_duplicate_or_alias` | 463 |
20
+ | `keep_structural_boundary` | 502 |
21
+ | `keep_path_boundary` | 5 |
22
+
23
+ Pi RPC stratified sample, `--threads 4`:
24
+
25
+ | stratum | rows | model result | note |
26
+ | --- | ---: | --- | --- |
27
+ | ambiguous merge, similarity `0.25..0.85` | 20 | 20 merge | Good. Reviewer cited concrete title-word pairings. |
28
+ | high-sim `keep_similar_duplicate_or_alias`, similarity `0.85..1.0` | 20 | 19 merge, 1 parse-error | Bad baseline heuristic. Examples such as `Asobi ni Iku yo!`, `Code Geass ~Akito the Exiled~`, `Yagate Kimi ni Naru`, and `Big Order` were wrongly kept separate by the heuristic. |
29
+ | structural keep | 20 | 18 keep, 1 merge, 1 skip | Mostly good. Covers category+title, multilingual aliases, and distinct fields. |
30
+ | path keep | 5 | 5 keep | Good. Covers path duplicate/category boundaries. |
31
+
32
+ Conclusion: `keep_similar_duplicate_or_alias` was too broad because it used max
33
+ literal similarity. Short connector repeats (`no`, `wa`, `ni`, `the`, `of`) and
34
+ repeated title words inflated similarity to `1.0` even for ordinary contiguous
35
+ title phrases.
36
+
37
+ ## Experiment 1: strict substantive duplicate keep
38
+
39
+ Change tried:
40
+
41
+ - Keep adjacent `TITLE SEP TITLE` only when top literals include a substantive
42
+ duplicate/near-duplicate pair.
43
+ - Ignore short connectors and literals with normalized length `< 6`.
44
+
45
+ Generated recipe output:
46
+
47
+ - `reports/dmhy_template_recipes.full_top5000.strictdup.seed.jsonl`
48
+
49
+ Decision distribution:
50
+
51
+ | decision | count |
52
+ | --- | ---: |
53
+ | `merge_dissimilar_title_continuation` | 4661 |
54
+ | `merge_adjacent_title_continuation` | 444 |
55
+ | `keep_similar_duplicate_or_alias` | 19 |
56
+ | `keep_structural_boundary` | 502 |
57
+ | `keep_path_boundary` | 5 |
58
+
59
+ Pi RPC review of all 19 remaining `keep_similar_duplicate_or_alias` decisions:
60
+
61
+ | rows | model result |
62
+ | ---: | --- |
63
+ | 19 | 19 merge |
64
+
65
+ Examples the reviewer judged as merge:
66
+
67
+ - `Air In Summer`, `Assault Lily BOUQUET`
68
+ - `Aoki Hagane no Arpeggio Ars Nova`
69
+ - `Shoujo Kageki Revue Starlight`
70
+ - `Motto! Ojamajo Doremi`
71
+ - `Fate stay night Heaven's Feel`
72
+ - `Arafou Otoko no Isekai Tsuuhan Seikatsu`
73
+ - `Watashi, Nouryoku wa Heikinchi de tte Itta yo ne!`
74
+
75
+ Conclusion: strict duplicate evidence still produced false keeps. The repeated
76
+ literal was often a real repeated word inside one title phrase or a top-literal
77
+ artifact, not a duplicate/alias boundary.
78
+
79
+ ## Experiment 2: merge all adjacent separator-only title slots
80
+
81
+ Final change:
82
+
83
+ - `TITLE PATH TITLE` remains `keep_path_boundary`.
84
+ - Non-separator structural boundaries remain `keep_structural_boundary`.
85
+ - Every separator-only adjacent title boundary, `TITLE SEP TITLE`, now merges.
86
+ - Low-similarity merges keep the old decision name
87
+ `merge_dissimilar_title_continuation`.
88
+ - High-similarity merges use `merge_adjacent_title_continuation`.
89
+ - `keep_similar_duplicate_or_alias` is no longer emitted by the deterministic
90
+ heuristic.
91
+
92
+ Generated recipe output:
93
+
94
+ - `reports/dmhy_template_recipes.full_top5000.mergeall.seed.jsonl`
95
+
96
+ Decision distribution:
97
+
98
+ | decision | count |
99
+ | --- | ---: |
100
+ | `merge_dissimilar_title_continuation` | 4661 |
101
+ | `merge_adjacent_title_continuation` | 463 |
102
+ | `keep_structural_boundary` | 502 |
103
+ | `keep_path_boundary` | 5 |
104
+
105
+ Pi RPC sample of high-similarity `merge_adjacent_title_continuation` decisions:
106
+
107
+ | rows | model result |
108
+ | ---: | --- |
109
+ | 30 | 29 merge, 1 parse-error |
110
+
111
+ The reviewer cited concrete title continuations such as:
112
+
113
+ - `Asobi ni Iku yo!`
114
+ - `Code Geass ~Akito the Exiled~`
115
+ - `Kami nomi zo Shiru Sekai`
116
+ - `Yagate Kimi ni Naru`
117
+ - `Big Order`
118
+ - `Gate Jieitai Kanochi nite Kaku Tatakaeri`
119
+ - `JoJo's Bizarre Adventure - Stardust Crusaders`
120
+
121
+ Conclusion: merge-all adjacent separator-only title slots is the best of the
122
+ tried heuristics. It matches Pi RPC evidence and simplifies downstream synthetic
123
+ title-span replacement.
124
+
125
+ ## Synthetic generation smoke
126
+
127
+ Generated schema v2 synthetic augmentation with merge-all recipes:
128
+
129
+ - `data/schema_v2_synthetic_aug.mergeall.jsonl`
130
+ - `data/schema_v2_synthetic_aug.mergeall.manifest.json`
131
+
132
+ Validator passed. Manifest summary:
133
+
134
+ | metric | value |
135
+ | --- | ---: |
136
+ | `generated_rows` | 49998 |
137
+ | `numeric_title_rows` | 30000 |
138
+ | `path_rows` | 19998 |
139
+ | `path_series_rows` | 15000 |
140
+ | `path_movie_rows` | 1666 |
141
+ | `path_special_rows` | 1666 |
142
+ | `path_confuser_rows` | 1666 |
143
+ | `dropped_media_kind_mismatch` | 19611 |
144
+
145
+ ## Commands
146
+
147
+ Pi RPC high-sim adjacent merge sample:
148
+
149
+ ```powershell
150
+ cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml -- `
151
+ --review-title-boundaries-pi-rpc `
152
+ --recipes reports\dmhy_template_recipes.full_top5000.mergeall.seed.jsonl `
153
+ --title-boundary-decisions-output reports\dmhy_title_boundary_pi_rpc_mergeall.adjacent_highsim.jsonl `
154
+ --title-boundary-heuristic-contains merge_adjacent_title_continuation `
155
+ --title-boundary-min-similarity 0.85 `
156
+ --title-boundary-max-similarity 1.0 `
157
+ --threads 4 `
158
+ --limit 30
159
+ ```
160
+
161
+ Synthetic smoke:
162
+
163
+ ```powershell
164
+ cargo run --release --manifest-path tools\schema_v2_synthetic_augment\Cargo.toml --bin schema_v2_synthetic_augment -- `
165
+ --recipes reports\dmhy_template_recipes.full_top5000.mergeall.seed.jsonl `
166
+ --label-schema-file label_schema.json `
167
+ --numeric-title-seeds data\synthetic_numeric_titles.txt `
168
+ --path-prefix-seeds data\synthetic_path_prefixes.txt `
169
+ --limit-templates 3000 `
170
+ --max-rows 50000 `
171
+ --output data\schema_v2_synthetic_aug.mergeall.jsonl `
172
+ --manifest-output data\schema_v2_synthetic_aug.mergeall.manifest.json
173
+
174
+ cargo run --release --manifest-path tools\schema_v2_synthetic_augment\Cargo.toml --bin validate_synthetic_aug_jsonl -- `
175
+ --input data\schema_v2_synthetic_aug.mergeall.jsonl `
176
+ --manifest data\schema_v2_synthetic_aug.mergeall.manifest.json
177
+ ```
tools/rust_dmhy_template_apply/README.md CHANGED
@@ -84,12 +84,13 @@ cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml --
84
  For stratified audits, select heuristic strata explicitly:
85
 
86
  ```powershell
87
- # High-similarity duplicate/alias keep candidates.
 
88
  cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml -- `
89
  --review-title-boundaries-pi-rpc `
90
  --recipes reports\dmhy_template_recipes.full_top5000.seed.jsonl `
91
- --title-boundary-decisions-output reports\dmhy_title_boundary_pi_rpc_highsim_keep.jsonl `
92
- --title-boundary-heuristic-contains keep_similar_duplicate_or_alias `
93
  --title-boundary-min-similarity 0.85 `
94
  --title-boundary-max-similarity 1.0 `
95
  --threads 4 `
 
84
  For stratified audits, select heuristic strata explicitly:
85
 
86
  ```powershell
87
+ # High-similarity adjacent-title merges. The old high-similarity keep heuristic
88
+ # was removed after Pi RPC review found it over-preserved title continuations.
89
  cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml -- `
90
  --review-title-boundaries-pi-rpc `
91
  --recipes reports\dmhy_template_recipes.full_top5000.seed.jsonl `
92
+ --title-boundary-decisions-output reports\dmhy_title_boundary_pi_rpc_adjacent_highsim.jsonl `
93
+ --title-boundary-heuristic-contains merge_adjacent_title_continuation `
94
  --title-boundary-min-similarity 0.85 `
95
  --title-boundary-max-similarity 1.0 `
96
  --threads 4 `
tools/rust_dmhy_template_apply/src/main.rs CHANGED
@@ -824,7 +824,7 @@ fn compute_title_span_metadata(
824
  let mut spans: Vec<Vec<usize>> = Vec::new();
825
  let mut decisions: Vec<Value> = Vec::new();
826
  let mut current_span: Vec<usize> = Vec::new();
827
- let similarity_keep_threshold = 0.72;
828
 
829
  for title_index in title_indices {
830
  if current_span.is_empty() {
@@ -850,12 +850,12 @@ fn compute_title_span_metadata(
850
  }));
851
  spans.push(current_span);
852
  current_span = vec![title_index];
853
- } else if only_separators_between && similarity < similarity_keep_threshold {
854
  decisions.push(json!({
855
  "left_role_index": previous,
856
  "right_role_index": title_index,
857
  "similarity": similarity,
858
- "decision": "merge_dissimilar_title_continuation",
859
  "source": "heuristic",
860
  }));
861
  current_span.push(title_index);
@@ -5325,7 +5325,7 @@ mod tests {
5325
  }
5326
 
5327
  #[test]
5328
- fn title_span_metadata_keeps_similar_duplicates_separate() {
5329
  let key = "TEXT SEP TEXT SEP EPISODE";
5330
  let roles = suggested_roles(key);
5331
  let mut cluster = Cluster::default();
@@ -5333,8 +5333,27 @@ mod tests {
5333
  cluster.position_literals[0].insert("Frieren".to_string(), 10);
5334
  cluster.position_literals[2].insert("Frieren".to_string(), 10);
5335
  let (spans, decisions) = compute_title_span_metadata(key, &roles, &cluster);
5336
- assert_eq!(spans.len(), 2, "{spans:?} {decisions:?}");
5337
- assert_eq!(decisions[0]["decision"], "keep_similar_duplicate_or_alias");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5338
  }
5339
 
5340
  #[test]
 
824
  let mut spans: Vec<Vec<usize>> = Vec::new();
825
  let mut decisions: Vec<Value> = Vec::new();
826
  let mut current_span: Vec<usize> = Vec::new();
827
+ let low_similarity_threshold = 0.72;
828
 
829
  for title_index in title_indices {
830
  if current_span.is_empty() {
 
850
  }));
851
  spans.push(current_span);
852
  current_span = vec![title_index];
853
+ } else if only_separators_between {
854
  decisions.push(json!({
855
  "left_role_index": previous,
856
  "right_role_index": title_index,
857
  "similarity": similarity,
858
+ "decision": if similarity < low_similarity_threshold { "merge_dissimilar_title_continuation" } else { "merge_adjacent_title_continuation" },
859
  "source": "heuristic",
860
  }));
861
  current_span.push(title_index);
 
5325
  }
5326
 
5327
  #[test]
5328
+ fn title_span_metadata_merges_similar_adjacent_titles() {
5329
  let key = "TEXT SEP TEXT SEP EPISODE";
5330
  let roles = suggested_roles(key);
5331
  let mut cluster = Cluster::default();
 
5333
  cluster.position_literals[0].insert("Frieren".to_string(), 10);
5334
  cluster.position_literals[2].insert("Frieren".to_string(), 10);
5335
  let (spans, decisions) = compute_title_span_metadata(key, &roles, &cluster);
5336
+ assert_eq!(spans.len(), 1, "{spans:?} {decisions:?}");
5337
+ assert_eq!(
5338
+ decisions[0]["decision"],
5339
+ "merge_adjacent_title_continuation"
5340
+ );
5341
+ }
5342
+
5343
+ #[test]
5344
+ fn title_span_metadata_merges_high_similarity_short_connectors() {
5345
+ let key = "TEXT SEP TEXT SEP EPISODE";
5346
+ let roles = suggested_roles(key);
5347
+ let mut cluster = Cluster::default();
5348
+ cluster.position_literals = vec![HashMap::new(); key.split_whitespace().count()];
5349
+ cluster.position_literals[0].insert("no".to_string(), 10);
5350
+ cluster.position_literals[2].insert("no".to_string(), 10);
5351
+ let (spans, decisions) = compute_title_span_metadata(key, &roles, &cluster);
5352
+ assert_eq!(spans.len(), 1, "{spans:?} {decisions:?}");
5353
+ assert_eq!(
5354
+ decisions[0]["decision"],
5355
+ "merge_adjacent_title_continuation"
5356
+ );
5357
  }
5358
 
5359
  #[test]