AI 评测分数很好,上线后却频繁出错,有时并不是模型突然变差,而是评测过程提前接触了答案。同一份客户工单的改写版,同时出现在提示词示例和最终测试集里,就可能让成绩显得过于乐观。

AI 数据泄漏是什么?工单评测的分组划分与重复检查方法示意图
分析数据提炼结论 · 原创方法示意图

这种问题叫数据泄漏。这里的“泄漏”指评测边界被破坏,与个人信息外泄不是同一个概念。本文用一个售后工单分类练习,说明怎样划分训练、验证与测试数据,怎样检查同源样本混入不同集合,以及哪些检查不能靠脚本完全解决。

最终交付物是一份分组清单、一段本地检查代码和一份评测记录。不需要购买模型服务,也不需要下载训练框架;涉及提示词调整和模型效果的部分属于方法说明,不冒充实际训练结果。

一、先区分三个集合各自负责什么

训练集用于让模型学习,或者为一项任务提供可参考的示例。在提示词工程中,放进 few-shot 示例的问答也参与了系统设计,不能再作为完全未见过的独立测试题。

验证集用于开发阶段比较方案。例如对比两个分类提示词、选择拒答规则、调整检索数量,都会让开发者逐渐了解这些题目。验证集可以反复使用,但它的结果主要服务于开发决策。

测试集用于方案确定后的最终检查。测试集的标签、答案与详细错误不应持续参与调参。看过错误后再修改提示词,当然可以继续改进系统,但这批题的角色已经发生变化,需要另留未参与修改的样本评估下一版。

这里没有适用于所有任务的固定比例。样本很少时,随意切出几条题目就宣称“准确率很稳定”并不可靠。先明确要估计什么能力,再讨论数据量、分组、时间范围和统计波动。

二、工单练习中,什么信息算提前知道答案

假设要把咨询分为退款、物流、使用问题三类。用户提交时可得到的字段是留言、订单状态与允许使用的业务资料。客服后续填写的“最终处理类型”“是否已退款”是在处理结束后才产生的信息,不能偷偷作为预测输入。

若模型从“已退款成功”推断出退款类别,离线成绩可能很好,但实际收到新工单时没有这个字段。这个差异属于信息可用时点错误:评测拿到了上线时不存在的条件。

另一个风险来自内容相似。原工单写“包裹三天没更新”,改写成“物流停了三天怎么办”,二者可以表达同一事件。把前者作为提示词示例、后者作为测试题,不一定是在测新场景,可能只是在测同一道题的文字变化。

同一客户、同一订单、同一文档及其切分片段,也可能有关联。分组键取决于任务目标:若要评估对新订单的能力,按订单分组更合理;若要评估全新客户,可能需要更高层级的客户分组。不要只为了让分数更高而选择分组方式。

三、制作一份可以复查的小样本清单

先用虚构数据,不放姓名、电话、真实订单号。每条记录保存样本编号、同源组、集合、输入文本及标签。编号标识单条记录,同源组把来自同一事件的原文和改写关联起来;两者不能混用。

下面的示例故意含两种错误:订单一的两条改写分别进入训练和测试;相同输入也同时进入两个集合。真实项目还应保存数据来源、采集时间与标签依据,便于追溯,而不是只留下脱离语境的句子。

rows = [
    {"id": "a1", "group": "order-1", "split": "train",
     "text": "包裹三天没更新", "label": "物流"},
    {"id": "a2", "group": "order-1", "split": "test",
     "text": "物流停了三天怎么办", "label": "物流"},
    {"id": "b1", "group": "order-2", "split": "train",
     "text": "想申请退款", "label": "退款"},
    {"id": "c1", "group": "order-3", "split": "test",
     "text": "想申请退款", "label": "退款"},
]

先建立来源关系,再划分集合。如果把数据随机分完才发现改写关系,后续清理会比较被动。使用 AI 扩写样本时,也应把所有衍生记录继承原样本的同源组,不把改写数量当成独立真实样本数量。

四、运行本地检查,找到可明确识别的交叉项

准备 Python 3。Windows 可用 py --version 检查;其他系统按实际安装方式使用 python3。本练习只用标准库,无需安装 scikit-learn。新建 UTF-8 文件 check_split.py,先放入上一段数据,再添加以下代码。

from collections import defaultdict
import unicodedata

def normalize(text):
    return " ".join(unicodedata.normalize("NFKC", text).split())

def find_crossings(records, key):
    buckets = defaultdict(set)
    for row in records:
        value = normalize(row[key]) if key == "text" else row[key]
        buckets[value].add(row["split"])
    return {value: sorted(splits)
            for value, splits in buckets.items() if len(splits) > 1}

print("同源组跨集合:", find_crossings(rows, "group"))
print("标准化文本跨集合:", find_crossings(rows, "text"))

在文件所在目录运行 py check_split.py。对应这四条示例,应发现 order-1 跨集合,以及“想申请退款”跨集合。文本标准化只统一部分字符形式与空白,不是语义去重模型,不应把没有报告的改写视为一定独立。

处理顺序也要明确。先把同源组调整到同一集合,再人工判断重复文本是重复采集、独立事件还是任务中本来就常见的问法。如果是两次独立真实咨询,不能在没有记录的情况下直接删除;需要解释保留方式及它对评测代表性的影响。

五、修正分组后,仍要检查哪些问题

可以把订单一的两条记录都移到训练集,把完全重复输入标为待复核,然后另外补入独立测试事件。不要为了让检查输出为空,随便修改分组编号或删掉不利样本。检查规则是用来暴露风险,不能反过来靠改标签掩盖风险。

脚本没有检查分布是否平衡。如果修正后测试集只剩退款类,分类准确率再高也不能说明物流与使用问题处理得好。要记录每类数量,包含模糊需求、信息不足、多个诉求与应拒答情形,确保它们符合真实使用场景。

对于随时间变化的业务,还要考虑时间边界。用后来发布的新政策回答过去时间点的问题,可能与计划评估的场景不一致;如果目标是下一阶段的真实业务,则可考虑按时间留出后段数据,避免把未来信息带回开发阶段。

还要注意输入清单本身的质量。示例默认字段齐全,真实文件应另外校验样本编号唯一、同源组不为空、集合名只允许训练、验证与测试等约定值。否则,一个拼错的集合名可能制造虚假的交叉提示,缺失分组也可能把不相关记录错误聚在一起。

保留一份修改前后的划分记录,注明为什么移动或排除某条样本。由另一位同事抽查部分记录的来源关系,比单纯扩大随机样本数量更容易发现流程错误。即使由同一个人负责,也可以隔开数据整理与最终评测步骤,减少看到答案后无意改变判断标准。

近似重复不能仅凭字符串相等判断。可先人工抽样,查看文本相似项的原始事件;数据量较大时再引入相似检索作为候选筛查。相似度阈值只是筛选条件,不能自动决定两条咨询是否来自同一个事件,更不能未经复核就删除客户记录。

六、传统模型和大模型应用的处理有何不同

scikit-learn 官方文档强调,预处理中的拟合也可能泄漏信息。均值填充、标准化和特征选择等步骤应只在训练部分拟合,再将同一变换应用到验证与测试数据,不能先对全量数据学习统计量再切分。

使用 Pipeline 可以减少把拟合范围写错的机会,但它不会替你识别同一订单的两条改写,也不会修复错误的时间字段。GroupShuffleSplit 按组划分数据;其测试比例面向组的数量,不等于最终样本行数一定达到同样比例。

大模型应用未必自行训练参数,边界仍然存在。把测试答案塞进检索文档、用测试题改写提示词、用错误样本编写新示例,都可能让后续成绩失去“独立检查”的含义。检索本来就应使用哪些资料,要按产品任务定义,不能把所有能检索到的答案一概视为泄漏。

七、怎样写一份不过度承诺的评测报告

报告至少包含任务定义、输入字段和可用时点、分组依据、每组数量、训练与测试版本、调参记录及已知限制。若用过测试反馈优化系统,写明这次结果属于开发期复测,不继续称为完全独立的最终测试。

当模型答错,保留原输入、预期标签、实际输出及判定理由,先确认标签有依据,再调整方案。不要只记录一个百分比;对少数但重要的拒答或越权案例,单独列出失败情况更有实际价值。

本文脚本完成的是一项数据划分审计练习。它可以发现明确的同源交叉与标准化文本重复,不能检测模型预训练语料,也不能证明系统已经没有泄漏。把能验证的范围写清楚,才方便团队持续改进并比较不同版本。

官方资料