Golden Set、真实样本与版本
正在读取学习记录…
Golden Set 是一组经过核对、可以反复使用的评测样本。每条样本至少包含输入、参考结果和判断规则。客服退款助手换了提示词后,拿同一批问题重新跑,才能知道它修好了什么,又弄坏了什么。
它像裁缝桌上的尺子:衣服可以改,尺子的刻度不能跟着改。若第一次用“7天内可退”,第二次悄悄改成“14天内可退”,分数变化就混入了政策变化,已经无法单独解释助手的进步。
从一条正常退款开始
预先约定一条业务规则:收到货后0至7天可退款,超过7天拒绝;天数缺失、为负数或类型错误则转人工。用户在第3天申请,参考结果是 approve(批准);第8天申请,参考结果是 reject(拒绝)。manual 表示转人工确认,仍是一种预先定义的合理结果。
“黄金”强调标签可靠,不代表样本全部简单,也不代表参考结果永远正确。订单事实、政策或任务定义发生变化时,要重新核对样本,产生新版本。实际业务样本通常来自脱敏后的工单与失败记录,还应保存采样渠道、时间窗口和授权范围;合成样本则保存生成规则,便于补边界。
下面六条输入都是代码中预置的合成数据。它们用于看清评测流程。四条开发样本覆盖第3、7、8天和缺失日期;两条留出样本覆盖第6天和负数。留出集(holdout)是一组留到选定方案后才查看的样本,相当于练习后做一张没有用来改答案的卷子。
后面的dev是development的缩写,表示开发集;baseline表示作为对照的旧实现,candidate表示待检查的新实现。它们是同一份样本上不同运行的标签,程序版本改变时不改参考标签。
语料身份与数据分区
id 是样本编号,便于失败时定位;group 是问题家族,用于把同一原始问题及其变体留在一个分区;split 是开发集或留出集;days 是退款输入;expected 是预先核定的参考结果。
开发集可以帮助调规则,留出集用来检查这项改动在另一批样本上是否还成立。同一订单的原文和改写分到两边,会让两组信息互相泄漏。代码按 group 检查跨分区问题;它不能自动识别两段自然语言是否实为同一问题,所以分组还需要采样环节维护。
语料版本给人阅读,内容摘要给程序核对。摘要来自整份规则与样本的稳定编码:更改标签、输入、分区或版本字符串,都会产生另一份摘要。可以把摘要理解为这份资料的内容指纹,核对它就能发现评测资料是否换过。
| 记录项 | 含义 | 同一份评测资料再跑一次时 |
|---|---|---|
| 语料版本,例如refund-golden-v1 | 给这份规则与样本起的名字 | 保持相同 |
| 内容摘要,例如ffb5cc87cf7b | 按代码中的完整资料计算出的指纹 | 保持相同 |
| 每次评测的运行编号与时间 | 区分哪一次执行,并找到它的结果 | 为新的执行单独记录 |
同一套题今天跑一遍、明天再跑一遍,题目身份保持相同。保存结果时,另记每次运行的编号、时间与被测程序版本,就能区分两份记录。例如,一份结果记录可以按“运行编号、时间、程序版本、语料版本、内容摘要、分区、通过数、失败样本”这几个字段保存,语料摘要仍只根据评测资料计算。
若把第7天的参考标签改了,内容摘要会变化,应复核标签并发布新语料版本;若只把被测程序的退款上限从6天修成7天,资料身份保持相同,变化的是程序版本与它得到的分数。
从空目录保存并运行
在 PowerShell 中进入你平时使用的学习目录,再创建本节文件夹并进入:
mkdir l7-golden
cd l7-golden用编辑器新建文件,把下面完整代码保存为当前文件夹的 golden_lab.py。保存类型选“所有文件”,编码选 UTF-8;资源管理器开启“文件扩展名”,核对文件名以 .py 结尾。数据已经写在代码里,可以直接运行。
"""冻结退款评测集,分别比较开发集和留出集的规则版本。"""
import hashlib
import json
# 全部样本均为预置合成数据;同一问题家族不能跨开发集和留出集。
DATASET = {
"version": "refund-golden-v1",
"policy": "收到货后0至7天可退款,缺失或非法天数转人工",
"cases": [
{"id": "g1", "group": "普通退货", "split": "dev", "days": 3, "expected": "approve"},
{"id": "g2", "group": "最后一天", "split": "dev", "days": 7, "expected": "approve"},
{"id": "g3", "group": "超过期限", "split": "dev", "days": 8, "expected": "reject"},
{"id": "g4", "group": "缺失日期", "split": "dev", "days": None, "expected": "manual"},
{"id": "h1", "group": "期限内另一日期", "split": "holdout", "days": 6, "expected": "approve"},
{"id": "h2", "group": "非法日期", "split": "holdout", "days": -1, "expected": "manual"},
],
}
def validate(dataset):
"""检查重复编号、标签和问题家族泄漏,防止错误语料混进分数。"""
if not dataset.get("version") or not dataset.get("cases"):
raise ValueError("版本和样本不能为空")
ids, group_splits = set(), {}
for case in dataset["cases"]:
if case["id"] in ids:
raise ValueError("样本编号重复")
ids.add(case["id"])
if case["split"] not in ("dev", "holdout"):
raise ValueError("未知数据分区")
if case["expected"] not in ("approve", "reject", "manual"):
raise ValueError("未知参考标签")
previous = group_splits.setdefault(case["group"], case["split"])
if previous != case["split"]:
raise ValueError("同一问题家族跨分区")
if {case["split"] for case in dataset["cases"]} != {"dev", "holdout"}:
raise ValueError("需要开发集和留出集")
def fingerprint(dataset):
"""稳定编码后计算摘要;内容或规则变化都会改变这份语料的身份。"""
validate(dataset)
encoded = json.dumps(dataset, ensure_ascii=False, sort_keys=True).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()[:12]
def decide(days, limit):
"""布尔值也属于Python整数家族,使用type避免把True当作第1天。"""
if type(days) is not int or days < 0:
return "manual"
return "approve" if days <= limit else "reject"
def evaluate(dataset, split, limit):
"""只比较指定分区;失败编号便于回到样本,而不只看平均值。"""
validate(dataset)
cases = [case for case in dataset["cases"] if case["split"] == split]
failed = [case["id"] for case in cases
if decide(case["days"], limit) != case["expected"]]
return len(cases) - len(failed), len(cases), failed
def main():
print(f"语料={DATASET['version']} 摘要={fingerprint(DATASET)}")
# baseline的6天上限故意漏掉第7天;candidate修正为7天。
for name, limit in (("baseline", 6), ("candidate", 7)):
for split in ("dev", "holdout"):
passed, total, failed = evaluate(DATASET, split, limit)
print(f"{name}/{split}: {passed}/{total}={passed / total:.0%} "
f"失败={','.join(failed) or '无'}")
if __name__ == "__main__":
main()回到刚才的 PowerShell,确认文件已经保存,再运行:
ls
python -X utf8 golden_lab.py程序输出:
语料=refund-golden-v1 摘要=ffb5cc87cf7b
baseline/dev: 3/4=75% 失败=g2
baseline/holdout: 2/2=100% 失败=无
candidate/dev: 4/4=100% 失败=无
candidate/holdout: 2/2=100% 失败=无每行分数怎样算出来
第一行给出语料版本 refund-golden-v1 和内容摘要 ffb5cc87cf7b,本次比较使用同一份输入与标签。baseline 的退款上限是6天,开发集仅 g2(第7天)失败;因此通过数为3,分母是该分区全部4条样本,3 ÷ 4 = 75%。
candidate 把上限修为7天,开发集变成4 ÷ 4 = 100%。两份规则的留出集都是2 ÷ 2 = 100%,说明这两条样本没有观察到区别。两条全过也可能只是样本覆盖有限;实际模型的质量估计还需要覆盖更多任务家族,并保留样本量和重复执行结果。
程序分四层工作:validate 拦住编号重复与分区泄漏;fingerprint 给语料内容生成摘要;decide 执行退款规则;evaluate 选中一个分区并保留失败编号。评分器不把 manual 一概当失败,它只看实际结果是否符合该样本的参考结果。
把第7天的修复撤回一次
在 main() 的版本列表里,把 ("candidate", 7) 改成 ("candidate", 6),保存后运行 python -X utf8 golden_lab.py。只看 candidate 的两行:开发集回到3/4,失败编号重新出现 g2;留出集仍为2/2。数据没动,所以摘要也不变。随后把7改回去再运行测试。
如果业务真的改成14天退款,需要先审核新规则及各条 expected,把语料版本更新后再比较两个实现。不能只改被测程序的上限,再把旧标签导致的失败说成模型退步。若把某条 holdout 的 group 改成“最后一天”,运行会直接报“同一问题家族跨分区”,这是语料检查发现泄漏。
做完泄漏对照后,将改过的group恢复为原来的问题家族;candidate上限保持7,语料版本与标签保持原始数据。重跑应得到原摘要及四行结果,再运行配套检查。
让评测集长期有用
持续查看留出集、再据此改提示词,会逐渐把它用成开发集。可以把已反复使用的失败样本纳入开发回归集,再建立一份新的留出集;不同语料版本的总分要同时列出版本、分区和样本量。真实样本、合成边界和攻击样本也应保留类别,以免某类样本数量过大把总体平均值推向它。
scikit-learn 的数据泄漏说明解释为什么测试集不能参与方案选择。再读 Anthropic 的评测任务设计部分,重点看明确输入与成功条件、从真实失败构建回归集的做法。
保存测试并检查关键条件
7项测试检查第7/8天边界、非法日期、共同分母、留出集独立计数、重复编号、问题家族泄漏和标签变化后的内容摘要。 将下方代码保存为同一文件夹的 test_golden_lab.py,运行:
ls
python -X utf8 -m unittest -v test_golden_lab.py看到 Ran 7 tests 与 OK 表示这些固定条件全部通过。若找不到文件,先用 dir 核对当前文件夹及两份文件名;若出现具体断言失败,先恢复正文中的代码与预置数据,再对照该项测试的条件。
完整测试:test_golden_lab.py
"""检查冻结语料、分区隔离和退款边界。"""
from copy import deepcopy
from pathlib import Path
import sys
import unittest
# 显式加入同目录,使独立运行包也能找到主程序。
sys.path.insert(0, str(Path(__file__).parent))
import golden_lab as lab
class GoldenTests(unittest.TestCase):
def test_day_seven_is_allowed(self):
self.assertEqual(lab.decide(7, 7), "approve")
self.assertEqual(lab.decide(8, 7), "reject")
def test_invalid_days_require_manual(self):
for value in (None, -1, True, "7", 7.0):
with self.subTest(value=value):
self.assertEqual(lab.decide(value, 7), "manual")
def test_baseline_and_candidate_share_denominator(self):
self.assertEqual(lab.evaluate(lab.DATASET, "dev", 6), (3, 4, ["g2"]))
self.assertEqual(lab.evaluate(lab.DATASET, "dev", 7), (4, 4, []))
def test_holdout_is_scored_separately(self):
self.assertEqual(lab.evaluate(lab.DATASET, "holdout", 7), (2, 2, []))
def test_duplicate_id_is_rejected(self):
changed = deepcopy(lab.DATASET)
changed["cases"][1]["id"] = "g1"
with self.assertRaises(ValueError):
lab.validate(changed)
def test_family_leakage_is_rejected(self):
changed = deepcopy(lab.DATASET)
changed["cases"][-1]["group"] = "最后一天"
with self.assertRaises(ValueError):
lab.validate(changed)
def test_reference_change_changes_fingerprint(self):
changed = deepcopy(lab.DATASET)
changed["cases"][1]["expected"] = "reject"
self.assertNotEqual(lab.fingerprint(changed), lab.fingerprint(lab.DATASET))
self.assertEqual(lab.fingerprint(deepcopy(lab.DATASET)),
lab.fingerprint(lab.DATASET))
if __name__ == "__main__":
unittest.main()课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 修改提示后要比较效果,最合适的样本用法是什么
修改提示后要比较效果,最合适的样本用法是什么?
对应知识点:L7-golden
简答题 · 解释机制
一条可重复评测的样本需要什么?
对应知识点:L7-golden
代码题 · 核对给定数据
把期望标签refund与实际标签shipping作相等断言会得到什么?
对应知识点:L7-golden
场景题 · 分析具体变化
基准集只包含退款题,能怎样补齐实际任务范围?
对应知识点:L7-golden
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。