
简介这份文档围绕ChatGPT技术的训练数据收集与标注方法展开面向自然语言处理入门者、AI产品从业者及对大模型训练流程感兴趣的学习者帮助读者理解高质量对话数据从何而来、如何标注与迭代优化。资源包共1个docx文件约37KB内容以文字讲解为主结构清晰便于快速通读与查阅。文档系统梳理了数据收集、数据标注与迭代调优三大环节数据收集部分介绍Web爬虫获取对话文本及数据过滤思路数据标注部分涵盖人工标注、标注指导与标注质量控制涉及分类、实体识别、意图识别等具体任务迭代调优部分说明如何通过持续对话评估提升模型鲁棒性与性能。文末附有知识点归纳方便读者把握重点。目前已有81人学习适合希望系统了解ChatGPT训练数据工程全流程的读者参考。1. 大模型训练数据从哪来一份可复现的收集与标注工程拆解很多人以为训练一个对话模型核心是算法和算力数据不过是“喂进去的语料”。真做过一轮就知道数据管线才是那个吃掉七成时间、还最容易翻车的地方。标题里说的“训练数据收集与标注方法”落到工程上其实是一整条流水线从哪里采、怎么洗、谁来标、标完怎么验、验完怎么存成训练能直接吃的格式。它解决的不是“有没有数据”而是“数据能不能支撑模型学会指令跟随、多轮对话和安全边界”。这套方法适合两类人一是想自己微调一个领域对话模型的开发者二是负责搭建数据标注流程的工程同学。下面按我实际搭过的一条链路讲能抄的部分直接给代码和参数。2. 数据收集先定来源边界再谈抓取量2.1 四类数据源的分工与取舍对话模型的训练数据常见做法是拆成四类各自解决不同问题混在一起采会很难评估质量。第一类是通用指令数据形态是“指令 输入 输出”用来教模型听懂任务。第二类是多轮对话数据重点是上下文连贯和角色一致。第三类是领域知识数据比如某行业的问答对用来补专业能力。第四类是偏好与安全数据形态是同一问题的多个回答加排序用来做对齐阶段。选型上有个反直觉的点不是越多越好。我一般会先定一个目标配比比如通用指令占六成、多轮占两成、领域占一成半、安全占半成然后按配比反推每类要采多少。这样做的原因是某一类过量会直接带偏模型的行为分布后面用再多清洗都救不回来。采集渠道上公开数据集、自有业务日志、人工构造是三条主路。自有日志价值最高但涉及隐私必须先做脱敏人工构造可控但成本高适合补长尾场景。这里不展开具体平台重点是把“来源—授权—脱敏”三件事在采集前就写进流程否则后面全是后悔药。2.2 用脚本做原始数据的批量拉取与去重采集阶段最容易忽略的是去重重复样本会让模型对某些句式过拟合。下面是一个最小可用的去重脚本基于文本哈希加 SimHash 近似去重。import hashlib from datasketch import MinHash, MinHashLSH def exact_dedup(records): seen set() result [] for r in records: h hashlib.md5(r[text].strip().encode(utf-8)).hexdigest() if h not in seen: seen.add(h) result.append(r) return result def near_dedup(records, threshold0.8, num_perm128): lsh MinHashLSH(thresholdthreshold, num_permnum_perm) minhashes {} for i, r in enumerate(records): m MinHash(num_permnum_perm) for token in r[text][:500]: # 只取前500字符做签名控制内存 m.update(token.encode(utf-8)) minhashes[i] m lsh.insert(i, m) keep set() for i, m in minhashes.items(): dup lsh.query(m) if len(dup) 1: keep.add(i) return [records[i] for i in sorted(keep)]逻辑说明exact_dedup用 MD5 做完全重复过滤成本低先跑一遍。near_dedup用 MinHash 生成文本签名再用 LSH 分桶找近似重复threshold0.8表示相似度超过 0.8 视为重复。num_perm128是签名长度越大越准但越慢一般 64 到 256 之间够用。只取前 500 字符是为了控制内存长文本场景可以改成按段落切分后再去重。参数上要注意阈值设太低会误删正常样本设太高去重不干净。我的经验是先用 0.85 跑一遍看保留率如果保留率低于七成说明阈值偏严往上调。2.3 脱敏与合规过滤的落地检查点脱敏不是简单替换手机号。要过一遍的至少包括身份证号、手机号、邮箱、银行卡号、详细地址、以及可能指向具体个人的组合信息。做法是用正则先粗筛再用规则加人工抽检。import re PATTERNS { phone: r1[3-9]\d{9}, id_card: r\d{17}[\dXx], email: r[\w.\-][\w\-]\.[\w.\-], bank_card: r\d{16,19}, } def mask_text(text): for name, pat in PATTERNS.items(): text re.sub(pat, f[{name.upper()}], text) return text逻辑说明每个正则对应一类敏感信息命中后替换成占位符而不是删除保留句式结构对训练更友好。参数上bank_card的 16 到 19 位容易误伤长数字串实际用的时候建议加前后边界判断比如要求前后不是数字。跑完正则后必须做人工抽检抽检比例不低于百分之一重点看有没有漏网的组合信息。注意脱敏和去重要在采集后立刻做不要等标注完再补。标注过的数据再脱敏等于让标注员白看了一遍敏感信息。3. 标注体系设计把“好回答”拆成可打分的维度3.1 标注维度的定义与打分粒度标注最怕的是“凭感觉标”。同一批数据两个人标出来不一致训练时就是噪声。解决办法是把质量拆成可独立判断的维度每个维度给明确的分档。我常用的是四个维度指令遵循度、事实准确性、表达流畅度、安全性。每个维度分三档比如指令遵循度分“完全满足 / 部分满足 / 未满足”。分档描述要写成一句话能判断的标准不能写“较好”“一般”这种模糊词。打分粒度上三档比五档更稳。五档看着精细实际标注员在中间档位上分歧最大一致性反而差。三档加一个“无法判断”兜底是性价比最高的方案。3.2 标注规范文档的写法与示例规范文档不是写给管理者看的是写给标注员看的。每一条都要有正例和反例。下面是一个维度定义的表格示例。维度档位判断标准正例要点反例要点指令遵循度完全满足回答覆盖指令全部要求问三点答三点漏答其中一点指令遵循度部分满足覆盖主要要求但有遗漏答了核心问题遗漏次要约束事实准确性准确关键事实可核实且无误数据、名称正确编造数据安全性安全无有害、歧视、诱导内容中性客观含攻击性表述写规范时有个血泪经验不要用“等等”“类似”这种开放词标注员会各自理解。每个档位配两到三个真实样例样例要从采集数据里挑不要自己编。3.3 标注任务的分发与一致性校验分发上同一条数据至少两人独立标注第三人仲裁分歧。一致性用 Cohens Kappa 衡量低于 0.6 说明规范有问题要回去改规范而不是怪标注员。from sklearn.metrics import cohen_kappa_score def check_agreement(labels_a, labels_b): kappa cohen_kappa_score(labels_a, labels_b) if kappa 0.6: return f一致性不足: {kappa:.2f}需修订规范 return f一致性可接受: {kappa:.2f}逻辑说明labels_a和labels_b是两个标注员对同一批样本的档位序列。Kappa 扣除了随机一致的概率比单纯算一致率可靠。参数上0.6 是常用门槛安全类维度建议提到 0.7因为安全判断分歧代价更高。跑完如果某维度特别低单独把该维度的样本拉出来复盘通常是档位描述有歧义。4. 从标注结果到训练格式转换脚本与字段校验4.1 对话样本的 JSONL 结构设计训练框架普遍吃 JSONL每行一个样本。对话样本的常见结构是消息列表每条消息带角色和内容。{messages: [{role: user, content: 帮我写一个排序函数}, {role: assistant, content: def sort_list(nums):\n return sorted(nums)}], meta: {source: instruction, quality: 3}}字段设计上messages是训练直接读的meta是给自己做数据分析和采样用的。quality存标注的综合档位方便后面按质量分层采样。不要把所有标注维度都塞进 meta选综合分加一两个关键维度就够字段太多会让文件膨胀。4.2 标注结果转训练格式的脚本标注结果通常是表格要转成上面的 JSONL。下面是一个转换脚本带字段校验。import json REQUIRED [instruction, response, quality] def convert_row(row): for k in REQUIRED: if k not in row or row[k] in (None, ): raise ValueError(f缺少字段: {k}) if row[quality] not in (1, 2, 3): raise ValueError(f质量档位非法: {row[quality]}) sample { messages: [ {role: user, content: row[instruction].strip()}, {role: assistant, content: row[response].strip()}, ], meta: {source: row.get(source, unknown), quality: row[quality]}, } return sample def build_jsonl(rows, out_path): with open(out_path, w, encodingutf-8) as f: for r in rows: try: f.write(json.dumps(convert_row(r), ensure_asciiFalse) \n) except ValueError as e: print(f跳过异常行: {e})逻辑说明convert_row先校验必填字段再校验质量档位范围任何一项不合法就抛错。build_jsonl逐行写入异常行打印后跳过而不是中断方便批量处理时定位问题。参数上ensure_asciiFalse保证中文不被转义文件体积更小也更可读。质量档位限定在 1 到 3和前面三档标注对齐。4.3 训练前的数据分布检查转完格式别急着训先看分布。至少看三个数总样本量、各质量档位占比、各来源占比。质量档位如果集中在最低档说明采集或标注有问题训出来效果不会好。from collections import Counter def check_distribution(path): quality Counter() source Counter() with open(path, encodingutf-8) as f: for line in f: obj json.loads(line) quality[obj[meta][quality]] 1 source[obj[meta][source]] 1 total sum(quality.values()) print(f总量: {total}) for k, v in quality.items(): print(f质量{k}: {v} ({v/total:.1%})) for k, v in source.items(): print(f来源{k}: {v} ({v/total:.1%}))逻辑说明用 Counter 统计两个关键分布输出占比。参数上没有需要调的重点看结果。我的经验是高质量档位占比低于两成时要么补采要么重新标硬训会浪费算力。来源分布如果某一类超过七成要考虑是不是采集脚本只跑通了那一路。5. 避坑与排查标注管线里最常见的五个翻车点5.1 标注员理解偏差导致整批数据报废现象某批次数据一致性 Kappa 只有 0.4两个标注员对同一维度判断差异极大。原因规范文档里档位描述用了“较好”这类模糊词标注员各自理解。解决把该维度描述改成可判断的具体条件补正反例重标该批次。规范修订后先小批量试标Kappa 过 0.6 再全量铺开。5.2 去重阈值过严误删有效样本现象去重后保留率只有五成明显偏低。原因SimHash 阈值设成 0.7短文本之间本来就相似被大量误判为重复。解决把阈值调到 0.85并对短于 50 字符的样本单独处理短文本只做完全重复过滤不做近似去重。5.3 脱敏正则误伤正常内容现象训练数据里出现大量[BANK_CARD]占位符但原文并没有银行卡号。原因bank_card正则匹配 16 到 19 位数字把长订单号、时间戳串也命中了。解决加前后边界判断要求匹配串前后不是数字并把订单号这类业务字段加入白名单。5.4 格式转换时静默丢数据现象转换后样本量比标注表少了一截但脚本没报错。原因异常行被except捕获后只打印批量跑的时候日志被刷过去没注意。解决把异常行单独写到一个错误文件转换结束后统计错误行数超过总量百分之一就中断并人工检查。5.5 质量档位分布失衡拖垮训练现象模型训完在简单指令上表现正常复杂指令一塌糊涂。原因质量分布里最低档占了七成模型学到的是“敷衍回答”的模式。解决按质量档位分层采样保证高质量档位在训练集里占比不低于三成不足的部分补采或重新标注。6. 进阶用分层采样和主动学习把标注成本压下来数据管线跑到后面瓶颈往往不是采不到而是标不起。全量人工标注成本太高我的做法是分层采样加主动学习把标注预算花在最有价值的样本上。分层采样的逻辑是先按来源和质量粗分再从每层里按比例抽。高质量层多抽低质量层少抽但保留一部分做负样本。这样训练集的质量分布可控不会因为随机采样被低质数据淹没。主动学习的做法是先用一小批已标注数据训一个初版模型让它对未标注数据打分挑出模型最不确定的样本优先送标。不确定度的衡量可以用输出概率的熵熵越高说明模型越没把握。import numpy as np def entropy_select(probs, top_k100): # probs: 模型对每个样本各类别的输出概率shape(n, num_classes) ent -np.sum(probs * np.log(probs 1e-9), axis1) idx np.argsort(ent)[::-1][:top_k] return idx逻辑说明entropy_select计算每个样本的预测熵熵越大越不确定取前top_k个送标。1e-9是防止 log 零的平滑项。参数上top_k按每轮标注预算定一般每轮几百到几千条。跑几轮后模型在难样本上的表现会明显提升而标注量可能只有全量的两三成。这套流程我自己跑下来最大的体会是数据管线里没有一步是可以跳过的但每一步都可以用脚本和规范把它变得可复现。规范写细一点脚本多校验一次后面就少一次返工。希望帮到你。本文还有配套的精品资源点击获取