news 2026/10/4 8:03:19

LLM Ops 评测与可观测实战(2):golden dataset 构建:线上数据的采样与标注流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLM Ops 评测与可观测实战(2):golden dataset 构建:线上数据的采样与标注流程

问题背景

上一篇用两组模拟数据说明了一把太小的尺子如何让 47% 的正确结论被判反,结论是评测集必须存在、规模要按可分辨差距倒推。这一篇解决"尺子上的刻度从哪来":一个跑了三个月的客服 RAG 机器人,日志里躺着八万多条真实会话,怎么从中提炼出三百来条能代表线上真实分布、标注质量可复核的金标准数据集(golden dataset)。直接照抄线上问题会踩两个坑:一是头部意图吃掉八成流量,随机抽出来评测集全是"退款多久到账",越界拒答、多轮追问这些恰恰最容易出事故的长尾一条没有;二是线上原话本身是脏的——错别字、半截话、夹带订单号的隐私串,不清洗不能用。本篇把整条流水线拆成四段:分层采样、隐私脱敏、近重复去重、双人标注加一致性仲裁,每段给出可操作的判据。

核心原理

**采样的第一原则是按业务意图分层,而不是按流量均匀。**均匀随机抽样的方差与层内样本量成反比,而流量分布天然头重脚轻:占比 3% 的多轮追问意图,在 300 条均匀样本里平均只有 9 条,单这一层的通过率估计误差就逼近 ±30 个百分点——等于这层没测。工程解法是带下限的分层抽样(stratified sampling with floor):大层按流量占比分配配额,小层强制保底(比如每层不少于 25 条),代价是评测集分布与线上分布有意的偏离,收益是每层都有最低可用分辨率。要注意这个偏离必须在报告里写明:分层评测的总分不再是"线上期望表现"的无偏估计,它是各层分数的加权平均,权重由你声明而不是流量决定。

**标注对象不是"正确答案"而是"判分合同"。**golden dataset 里每条用例的真正资产是它的评分契约:参考回答要点(必须出现哪些事实)、负向约束(出现哪些内容直接判错,比如承诺具体到账日期)、以及分类标签(该条属于哪个意图、难度、是否含陷阱)。三个人独立对同一批 50 条写要点,交集部分就是可自动化的硬规则,差集部分暴露的是需求理解分歧——标注流程顺带产出的这份"分歧清单",往往比数据集本身更有价值。

**一致性用双标 + 仲裁闭环控制。**每条用例至少两人独立标注,不一致的进仲裁;批次级别跟踪标注者间一致性(下一篇会用 Cohen’s kappa 给出算法)。经验阈值:原始一致率低于 80% 的批次整批返工,因为这说明标注指南本身有洞,修补指南比返工标注便宜。

**去重要发生在标注之前。**近重复问题(“退款多久到账"与"退款大概几天能到呢”)不仅浪费标注预算,还会让评测集对某个高频措辞隐性过采样。字符级 n-gram 的 Jaccard 相似度在中文短query上简单有效:阈值 0.8 上下能把同一政策的换皮问法并进一个簇,每簇只标一条。

实验一:分层抽样 vs 均匀随机,长尾层的分辨率差多少

本机以确定性模拟演示采样机制(固定种子),生产用真实日志与抽样脚本。

importrandom INTENTS={"policy_qa":0.42,# 售后政策问答"order_query":0.28,# 订单/物流查询"complaint":0.12,# 投诉安抚"chitchat":0.09,# 闲聊跑题"edge_reject":0.06,# 应拒答的越界问题"multiturn_followup":0.03,# 多轮追问改写}FLOOR=25# 每层最低样本量(小意图保底)TARGET=300# 评测集目标规模pool=[]rng=random.Random(7)forintent,shareinINTENTS.items():for_inrange(int(5000*share)):pool.append((intent,"q_%s_%04d"%(intent,len(pool))))defstratified(pool,target,floor,seed):rng=random.Random(seed)by_intent={}foriteminpool:by_intent.setdefault(item[0],[]).append(item)picked=[]forintent,itemsinsorted(by_intent.items()):quota=max(floor,round(target*len(items)/len(pool)))picked+=rng.sample(items,min(quota,len(items)))returnpickeddefnaive(pool,target,seed):rng=random.Random(seed)returnrng.sample(pool,target)strat=stratified(pool,TARGET,FLOOR,seed=11)naive_set=naive(pool,TARGET,seed=11)defdist(sample):c={}forintent,_insample:c[intent]=c.get(intent,0)+1returncprint("意图 | 线上占比 | 分层抽样 | 均匀随机抽样")ds,dn=dist(strat),dist(naive_set)forintent,shareinINTENTS.items():print("%-20s | %6.1f%% | %7d | %7d"%(intent,share*100,ds.get(intent,0),dn.get(intent,0)))print("分层抽样总数 %d, 均匀随机总数 %d"%(len(strat),len(naive_set)))

运行输出:

意图 | 线上占比 | 分层抽样 | 均匀随机抽样 policy_qa | 42.0% | 126 | 139 order_query | 28.0% | 84 | 75 complaint | 12.0% | 36 | 40 chitchat | 9.0% | 27 | 19 edge_reject | 6.0% | 25 | 17 multiturn_followup | 3.0% | 25 | 10 分层抽样总数 323, 均匀随机总数 300

对比最后两行:均匀随机下多轮追问层只有 10 条(上一篇的置信区间表告诉我们,这个规模的通过率半宽超过 ±28 个百分点,纯属装饰),越界拒答层 17 条同样不够看;分层保底后两层各 25 条,总数只多花 23 条预算,长尾分辨率却从不存在变成可用。另一个细节是"总数 323 > 300":下限配额会让总量微超目标,工程上接受这个超支,或者从最大层里扣回——但绝不为凑整数砍掉小层。

实验二:标注前去重,把同一政策的换皮问法并进一簇

importrandom BASE_QUERIES=["七天无理由退货怎么操作","退款多久能到账","订单什么时候发货","发票开错了能重开吗","会员积分怎么兑换","跨境商品支持退换货吗","商品价格保价规则是什么","客服人工电话是多少",]SUFFIXES=["?","呢","啊",",急",",谢谢","麻烦告诉我","在线等"]rng=random.Random(5)candidates=[]forqinBASE_QUERIES:candidates.append(q)forsuffixinrng.sample(SUFFIXES,3):mutated=q+suffixifrng.random()<0.4:# 再抖掉一两个字制造脏变体pos=rng.randrange(len(mutated)-1)mutated=mutated[:pos]+mutated[pos+1:]candidates.append(mutated)extra=rng.randrange(len(BASE_QUERIES))# 另一主题的干净样本candidates.append(BASE_QUERIES[extra][:-2])defshingles(s,k=3):return{s[i:i+k]foriinrange(max(len(s)-k+1,1))}defjaccard(a,b):returnlen(a&b)/len(a|b)if(a|b)else0.0threshold=0.8clusters=[]# 每簇保留第一个成员作为代表shingle_cache=[shingles(q)forqincandidates]foridx,qinenumerate(candidates):placed=Falseforclusterinclusters:ifjaccard(shingle_cache[cluster[0]],shingle_cache[idx])>=threshold:cluster.append(idx)placed=Truebreakifnotplaced:clusters.append([idx])kept=[clusters[c][0]forcinrange(len(clusters))]print("候选 %d 条 -> 去重后保留 %d 条(阈值 Jaccard>=%.1f)"%(len(candidates),len(kept),threshold))sizes=sorted((len(c)forcinclusters),reverse=True)print("簇大小分布 Top5:",sizes[:5])print("示例簇(代表 | 被合并的近似重复):")biggest=max(clusters,key=len)print(" 保留:",candidates[biggest[0]])forminbiggest[1:]:print(" 合并:",candidates[m])

运行输出:

候选 40 条 -> 去重后保留 27 条(阈值 Jaccard>=0.8) 簇大小分布 Top5: [4, 3, 3, 2, 2] 示例簇(代表 | 被合并的近似重复): 保留: 跨境商品支持退换货吗 合并: 跨境商品支持退换货吗啊 合并: 跨境商品支持退换货吗呢 合并: 跨境商品支持退换货吗,谢

40 条候选压成 27 条,三成预算省下来了;更重要的是簇大小分布本身就是流量结构的信息——"跨境退换货"聚成 4 条簇,说明该政策有歧义、用户变着法问,这反而是提示该层要加权采样的信号。两点实操提醒:真实日志里 Jaccard 阈值建议从 0.8 起调,人工抽检 50 个合并决策再定,中文短文本对 3-gram 敏感,0.7 以下开始误伤同主题不同意图的问句;数据量上万后逐对比较是平方复杂度,换 MinHash 或倒排块(blocking)做候选对生成。

流水线落地四步

第一步,导出与脱敏先于行:日志落地评测工作区之前过一遍正则加实体识别,手机号、地址、订单号一律替换为保留格式的假值(138****0001这类),脱敏规则本身要 review——“帮我查订单 20240713xxxx"里的日期串经常被漏掉,但它能关联到真人。第二步,分层配额表评审:业务方、标注方、算法方对每张意图层的配额和目标占比签字,避免出分后争论"为什么闲聊占一成”。第三步,标注平台留痕迹:每条用例记录标注人、时长、修改轮次,纯秒标与十分钟标的可信度不是一个量级,事后质检按这个元数据抽查。第四步,季度滚动更新:每季度从线上新意图里补 20% 新样本、同时把过时的样本降级为"归档可见、不进总分",防止评测集老化成"2024 年的互联网"。

常见陷阱

其一,标注指南用形容词:“回答有帮助"没有可操作性,指南必须写成"若回答包含 X 且未包含 Y 则判通过"级别。其二,让最懂业务的人单干标注:专家标注最准,但单点不可复核、进度是瓶颈,且专家脑中的标准没写进指南就永远是黑箱——双标的意义一半在质量一半在逼专家把知识外化。其三,评测集里保留隐私原话"以便还原现场”:金标准数据集会被很多工具和很多人访问,脱不干净的样本直接不进集。其四,把失败案例只修不存:处理完一个线上投诉就删掉现场,等于每个 bug 只付一次学费,正确动作是清洗后进评测集。其五,去重用力过猛:把"支持退换货吗"与"退换货要多久"并成一簇,两个意图被一个合并决策吃掉,所以合并必须抽检。

落地清单

  • 按意图分层设配额并给小层保底(≥25 条),报告里声明加权方式
  • 全量脱敏先于一切,规则评审覆盖"能关联到真人"的组合字段
  • 每条用例写"判分合同":必含要点 + 负向约束 + 意图/难度标签
  • 双人独立标注,批次一致率 < 80% 整批返工并修指南
  • 标注前 n-gram Jaccard 去重(0.8 起步,人工抽检合并决策)
  • 季度补样 20%,线上坏案例清洗后入集

数据集有了,接下来的瓶颈立刻转移到打分:三百条用例每次变更都跑一遍,谁来判每一条的好坏?人判太快就糙、太准就贵。下一篇《LLM Ops 评测与可观测实战(3):自动打分:规则评分、LLM-as-judge 与一致性校验》给出三层打分器架构,以及怎么证明你的自动打分器不是在瞎打分。

参考来源

  • Wikipedia:Stratified sampling:https://en.wikipedia.org/wiki/Stratified_sampling
  • Wikipedia:Jaccard index:https://en.wikipedia.org/wiki/Jaccard_index
  • Wikipedia:MinHash:https://en.wikipedia.org/wiki/MinHash
  • Wikipedia:Inter-rater reliability:https://en.wikipedia.org/wiki/Inter-rater_reliability
  • LangSmith 评测与数据集文档:https://docs.smith.langchain.com/
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 8:01:19

光伏组件热斑与缺陷检测数据集 | 光伏组件 热斑检测 PID识别 红外检测 光伏巡检9148期

光伏组件热斑与缺陷检测数据集 | 光伏组件 热斑检测 PID识别 红外检测 光伏巡检9148期 数据集概述 本数据集专注于红外热成像下的光伏组件热斑与缺陷检测&#xff0c;服务于光伏电站智能巡检、故障诊断及运维管理。数据涵盖热斑、潜在缺陷及光伏组件区域&#xff0c;适配无人机…

作者头像 李华
网站建设 2026/10/4 7:59:01

在家居士略感

作为一个在家居士&#xff0c;余生该如何度过&#xff0c;如何安排好这一生&#xff0c;一方面需要做一个合格的人&#xff0c;一方面还需要不忘记回家的路&#xff0c;念佛回归极乐&#xff0c;佛法说&#xff0c;世间法与佛法是不二&#xff0c;随其心净则土净&#xff0c;当…

作者头像 李华
网站建设 2026/10/4 7:58:18

ZYNQ上KSZ9031 PHY调试:MDIO与MMD读取0xFFFF的排查实践

最近在一块 ZYNQ-7000 板卡上调千兆以太网&#xff0c;PS 端 GEM0 外接的是 Microchip 的 KSZ9031RNX&#xff0c;软件层面用裸机 LwIP 跑 TCP/IP 协议栈。原本觉得这套组合很常见&#xff0c;结果调试日志直接停在PHY init failed&#xff0c;用XEmacPs_PhyRead读 PHY ID 寄存…

作者头像 李华
网站建设 2026/10/4 7:56:42

ZYNQ无DDR运行方案:OCM片上存储器的启动流程与链接脚本实战

1. 为什么要折腾“不带DDR的ZYNQ”&#xff1a;OCM在无外置内存方案中的真实位置1.1 先搞清楚ZYNQ里OCM到底是什么ZYNQ这类芯片和普通单片机最大的不同&#xff0c;就是它内部集成了ARM Cortex-A9双核处理器&#xff0c;但这并不意味着离开了外部DDR它就不会工作。很多人拿到Vi…

作者头像 李华
网站建设 2026/10/4 7:55:33

Calibre License失效真相:开源软件的License认知误区与HOSTID排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华