|
1 | | -"""Recognition post-processing jobs: n-best LM rescoring.""" |
| 1 | +"""Recognition post-processing jobs: n-best LM and CTC rescoring.""" |
2 | 2 |
|
3 | | -__all__ = ["NbestKenLmRescoreJob"] |
| 3 | +__all__ = ["NbestKenLmRescoreJob", "NbestCtcRescoreJob"] |
4 | 4 |
|
5 | 5 | import ast |
6 | 6 | import math |
@@ -74,3 +74,66 @@ def run(self): |
74 | 74 | for seq_tag, text in results.items(): |
75 | 75 | f.write(f"{repr(str(seq_tag))}: {repr(text)},\n") |
76 | 76 | f.write("}\n") |
| 77 | + |
| 78 | + |
| 79 | +class NbestCtcRescoreJob(Job): |
| 80 | + """Fuse n-best AM scores with per-hypothesis CTC sequence scores, best hyp per sequence, once per ``ctc_scale``.""" |
| 81 | + |
| 82 | + def __init__( |
| 83 | + self, |
| 84 | + *, |
| 85 | + nbest_file, |
| 86 | + ctc_scores_file, |
| 87 | + ctc_scales, |
| 88 | + am_scale: float = 1.0, |
| 89 | + ): |
| 90 | + # both files are {seq_tag: [(score, text), ...]} with matching seq_tags and per-seq hyp order |
| 91 | + self.nbest_file = nbest_file |
| 92 | + self.ctc_scores_file = ctc_scores_file |
| 93 | + self.ctc_scales = tuple(float(s) for s in ctc_scales) |
| 94 | + self.am_scale = float(am_scale) |
| 95 | + |
| 96 | + self.out_search_results = { |
| 97 | + cs: self.output_path(f"search_out.ctc{cs}.py") for cs in self.ctc_scales |
| 98 | + } |
| 99 | + |
| 100 | + self.rqmt = {"cpu": 1, "mem": 4, "time": 1} |
| 101 | + |
| 102 | + def tasks(self): |
| 103 | + yield Task("run", rqmt=self.rqmt, mini_task=True) |
| 104 | + |
| 105 | + def run(self): |
| 106 | + with open(self.nbest_file.get_path(), "rt") as f: |
| 107 | + nbest = ast.literal_eval(f.read()) |
| 108 | + with open(self.ctc_scores_file.get_path(), "rt") as f: |
| 109 | + ctc = ast.literal_eval(f.read()) |
| 110 | + |
| 111 | + assert set(nbest) == set(ctc), ( |
| 112 | + f"seq_tag mismatch: {len(set(nbest) ^ set(ctc))} tags differ between nbest and ctc scores" |
| 113 | + ) |
| 114 | + |
| 115 | + joined = {} |
| 116 | + for seq_tag, am_entries in nbest.items(): |
| 117 | + ctc_entries = ctc[seq_tag] |
| 118 | + assert len(am_entries) == len(ctc_entries), seq_tag |
| 119 | + lst = [] |
| 120 | + for (am_score, am_text), (ctc_score, ctc_text) in zip(am_entries, ctc_entries): |
| 121 | + assert am_text == ctc_text, f"{seq_tag}: hyp text mismatch between nbest and ctc scores" |
| 122 | + lst.append((float(am_score), float(ctc_score), am_text)) |
| 123 | + joined[seq_tag] = lst |
| 124 | + |
| 125 | + for ctc_scale in self.ctc_scales: |
| 126 | + results = {} |
| 127 | + for seq_tag, lst in joined.items(): |
| 128 | + best_text, best_score = "", None |
| 129 | + for am_score, ctc_score, text in lst: |
| 130 | + score = self.am_scale * am_score + ctc_scale * ctc_score |
| 131 | + if best_score is None or score > best_score: |
| 132 | + best_score, best_text = score, text |
| 133 | + results[seq_tag] = best_text |
| 134 | + |
| 135 | + with open(self.out_search_results[ctc_scale].get_path(), "wt") as f: |
| 136 | + f.write("{\n") |
| 137 | + for seq_tag, text in results.items(): |
| 138 | + f.write(f"{repr(str(seq_tag))}: {repr(text)},\n") |
| 139 | + f.write("}\n") |
0 commit comments