|
14 | 14 |
|
15 | 15 | use crate::asr_rescore::strip_punct; |
16 | 16 |
|
| 17 | +/// CER 单侧长度上限(字符):levenshtein 为 O(n×m) 时间(滚动行内存安全), |
| 18 | +/// 超长文本(≥20k 字 ≈ 半小时以上口播)在工具路径会消耗分钟级 CPU—— |
| 19 | +/// 审查修复(2026-09-05):超限诚实返回 None(不可比),由调用方决定跳过; |
| 20 | +/// 画像侧更严护栏见 eval_confusion(2000)。harness 样本段级测量不受影响。 |
| 21 | +const CER_MAX_CHARS: usize = 20_000; |
| 22 | + |
17 | 23 | /// CER 计算(纯函数):编辑距离 / 参考字符数。 |
18 | 24 | /// |
19 | 25 | /// @ai-context: 返回 0.0~1.0+(插入错误可使 CER >1,如实反映退化);参考文本 |
20 | | -/// 为空 → None(无法计算——诚实表达,不返回假 0)。 |
| 26 | +/// 为空 → None(无法计算——诚实表达,不返回假 0);任一侧超 |
| 27 | +/// CER_MAX_CHARS → None(时间成本护栏,诚实不可比)。 |
21 | 28 | pub fn cer(reference: &str, hypothesis: &str) -> Option<f32> { |
22 | 29 | let ref_chars: Vec<char> = strip_punct(reference); |
23 | 30 | let hyp_chars: Vec<char> = strip_punct(hypothesis); |
24 | 31 | if ref_chars.is_empty() { |
25 | 32 | return None; |
26 | 33 | } |
| 34 | + if ref_chars.len() > CER_MAX_CHARS || hyp_chars.len() > CER_MAX_CHARS { |
| 35 | + return None; |
| 36 | + } |
27 | 37 | let dist = crate::asr_rescore::levenshtein( |
28 | 38 | &ref_chars.iter().collect::<String>(), |
29 | 39 | &hyp_chars.iter().collect::<String>(), |
@@ -80,6 +90,14 @@ mod tests { |
80 | 90 | assert_eq!(cer(" ", "有内容"), None); |
81 | 91 | } |
82 | 92 |
|
| 93 | + #[test] |
| 94 | + fn cer_overlong_side_is_none_guard() { |
| 95 | + // 超长护栏(审查修复):任一侧超 20k 字 → None(O(n×m) 时间成本诚实不可比) |
| 96 | + let long = "字".repeat(CER_MAX_CHARS + 1); |
| 97 | + assert_eq!(cer(&long, "短"), None); |
| 98 | + assert_eq!(cer("短", &long), None); |
| 99 | + } |
| 100 | + |
83 | 101 | #[test] |
84 | 102 | fn cer_punctuation_ignored() { |
85 | 103 | // 标点差异不计入错误(与 strip_punct 同口径) |
|
0 commit comments