迭代记录我怎么修正自己的评测体系
项目:AI 内容生产 Agent 的质量评测体系 · v1.1 → v1.2
| 验证项 | 修复前 | 修复后 |
|---|---|---|
| 短稿违规是否触发否决 | 否(红线被旁路) | 是 ✅ |
| 规则分取值数 | 3 个 | 7 个 ✅ |
| 严重度能否区分 | 踩 1 条 = 踩 2 条 | 1 条 59 / 2 条 54 ✅ |
| C1 的召回盲区 | 无度量 | 可诊断 ✅ |
| 改写 vs 无关的区分余量 | −0.018(无法区分) | +0.288 ✅ |
| 回归测试 | 无 | 3 组 ✅ |
判分器有个护栏:当输入明显不是纯口播稿时,不给出误导性的分数。判断条件是"违规比例 > 60%"。
// 原实现 low_confidence = total_numbers > 0 and violation_count / total_numbers > 0.6 if low_confidence: result["status"] = "结果不可用(低置信度)" result["veto"] = False // ← 把一票否决关掉了
问题:样本量很小时,比例极易达到 100%。
一篇 300 字的正常短稿只要出现 1 个无出处数字 → 总数字 1、违规 1 → 比例 100% → 触发护栏。
结果不是"违规",而是"结果不可用",并且 veto 被置为 False。
修复:两处改动,各有明确理由。
// ① 加绝对样本量下限:样本不足时只提示、不降级 C3_MIN_SAMPLE_FOR_GUARD = 4 guard_triggered = (total_numbers >= C3_MIN_SAMPLE_FOR_GUARD and ratio > C3_GUARD_RATIO) // ② 置信度与 veto 解耦:置信度只决定"分数能不能用",永远不能关闭违规结论 result["veto"] = bool(violations)
人工和判分器判的是同一批被抽取出来的候选。没被抽出的,永远进不了混淆矩阵。
假设脚本里真实有 5 个自创类比:
违规 A(含"就像") → 进入候选 → 判分器正确判违规 → ✅ TP 违规 B(含"就好比") → 进入候选 → 判分器正确判违规 → ✅ TP 违规 C(措辞罕见) → 未被抽出 → 从未被判定 → 直接"通过"流出 → ❌ 不进矩阵 违规 D(措辞罕见) → 同上 违规 E(措辞罕见) → 同上 表格报的: 0/2 = 0% ← 分母只有候选集里的 2 个 真实情况: 3/5 = 60% ← 分母应该是全文的 5 个
这不是参数没调好,是指标结构上看不见这类错误。 "一致率 ≥80%" 无法证明 C1 拦得住红线——因为未被抽出的假阴性不在任何指标的分母里。
修复:新增召回率诊断,只诊断、不改变判定结论(含标记的句子未必都是类比)。
| 输出字段 | 含义 |
|---|---|
coverage | 含标记的句子中,实际参与判定的比例 |
unextracted_marked | 含标记但未参与判定的句子(需人工确认是不是类比) |
possible_missed_nonmarker | 无标记词但形态像类比的句子 —— 直接暴露标记词表的盲区 |
扣分逻辑只对 C2/C4/C5/C6 生效,但 C2/C5 压根没被调用,C6 当时没有计数字段 → 恒不扣分。
distinct rule_score values = [59, 90, 100] C1=0 C3=0 C4miss=0 -> 100 C1=0 C3=0 C4miss=1 -> 90 C1=1 C3=0 C4miss=0 -> 59 C1=1 C3=1 C4miss=0 -> 59 ← 踩 1 条红线 = 踩 2 条红线 C1=1 C3=1 C4miss=1 -> 59
三个问题:① 取值只有 3 个,撑不起"版本对比";② 踩 1 条红线和踩 2 条同分,严重度无法区分;③ C2/C5 的扣分分支永远不会执行。
修复:C6 初筛产出计数字段纳入扣分;一票否决按命中项数体现严重度;新增 score_scope 显式声明哪些维度参与计分。
score_scope 的价值:不会再出现"看起来是 6 项综合分,实际只有 2 项在扣分"的误读。
原方法用字符二元组 Jaccard 算相似度。构造四档改写强度实测:
| 改写强度 | 字符基线(原) | LSA 语义匹配(新) |
|---|---|---|
| ① 库内原句 | 1.000 ✅ 命中 | 1.000 ✅ 命中 |
| ② 轻度改写 | 0.855 ✅ 命中 | 0.998 ✅ 命中 |
| ③ 中度改写(换词不换义) | 0.059 ❌ | 0.833 ✅ |
| ④ 重度改写 | 0.020 ❌ | 0.709 ✅ |
| ⑤ 完全无关类比 | 0.038 | 0.421 |
| 改写最低 − 无关 的余量 | −0.018 | +0.288 |
修复:离线语义检索,纯 numpy 实现,不依赖任何外部 API。
| 层 | 实现 | 替代了什么 |
|---|---|---|
| 分词 | 自写 tokenizer(中文字 + bigram + 英文/数字词) | 不需要 jieba |
| 向量化 | 自写 TF-IDF(sublinear tf + 平滑 idf + L2 归一化) | 替代 sklearn |
| 语义空间 | 自写幂迭代 SVD → LSA 潜在语义分析 | 替代 sentence-transformers |
| 相似度 | 余弦相似度 | — |
三个设计决策,依据都来自业务需求(不是技术惯例):
| 决策 | 选择 | 理由 |
|---|---|---|
| chunk 粒度 | 一条类比 = 一个 chunk | C1 的判定单位就是"一条类比"。若 chunk 是整段,向量是整段的平均语义 → 检索不准 |
| 双重向量 | 原句 + 改写模板各一个向量,取较高分 | 库里既有原句也有抽象模板,两者都能命中 |
| 元数据 | 每条携带来源篇号 / 分类 / 模板 | C1 判定需指出"类比来自哪一篇",否则无法追溯 |
判分器是测量内容合格与否的尺子。尺子本身不准,后面跑出的所有质量分、所有改善结论都不成立。 所以顺序是:先把判据定死并校准 → 再接入生产链路。这个顺序是有意的取舍,不是遗漏。
C3 那个缺陷给我的教训是:"这个分数能不能用"和"内容合不合格"是两件事,必须解耦。 混在一起的后果是——系统用"我算不准"为理由,放过了"确实违规"。
红线项的指标方向一律是"越低越好"(漏放率),不是"越高越好"(一致率)。 标定时必须按"漏放优先"选点:先保证漏放率≈0,再在满足条件的区间里选误拦最低的。
字符相似度的余量是 −0.018。任何阈值都救不了一个负余量。 遇到这种情况,要做的是换方法。
和"能代码判的绝不用模型判"是同一个原则。离线实现让结果可复现、零成本、断网可用、现场可演示。
| 序 | 动作 | 目的 |
|---|---|---|
| 1 | 把语义检索接入判定链路,加 --embedding 开关 | 让修复真正作用到判定,支持 A/B 对比 |
| 2 | 人工通读全文标出所有类比,标定抽取召回率 | 补上红线验证的最后一环 |
| 3 | 扩阈值标定样本到 ≥100 条,按"漏放优先"重选阈值 | 把 0.25 从"实测值"变成"标定值" |
| 4 | 为保守保留的 18 条补写模板 | 模板覆盖率 22% → 提升,直接提升召回 |
| 5 | 跑 30 条用例 → 人工标真值 → 填一致率表 | 拿到三个真实数字 |
| 6 | 补 C2/C5 的判定(先人工标 10 条验证一致率) | 让合格率的分母完整 |
# 判分器自测 + 3 组回归测试 python run_eval.py --self-test # 两种相似度方法的对比验证 python _verify_lsa.py