做自然语言处理项目的人大概都有同感:真正卡住进度的往往不是模型结构,而是数据。几个月前我接了个企业内部知识库问答系统的训练任务,手里只有300多条人工整理的问答对,模型怎么调都欠火候。后来我换了个思路,用NeMo Data Designer把数据规模从几百条扩到了5万条,训练出来的效果反而明显更好。这篇文章就围绕我这次的完整实操过程来写,聊聊如何用这套工具从零构建高质量合成数据集,希望能够帮到同样在数据问题上挣扎的朋友。
1. 为什么需要合成数据:从真实数据到数据增强的关键一步
先说结论:合成数据不是要替代真实数据,而是把真实数据的能力放大。模型训练这事有个很朴素的规律,数据量和分布决定了下限。但真实世界的数据从来都不是理想的:标注成本高、隐私限制多、覆盖不均衡。
拿我那个知识库项目来说,300多条QA对连一个像样的验证集都分不出来,更不要说训练了。人工标注的成本大家心里有数,一条质量过得去的客服问答,需要业务人员写初稿、审核人员改措辞,人力和时间都投进去,一天也出不了多少条。如果数据还涉及客户信息和内部业务数据,想外包给第三方标注团队,合规那一关就过不去。至于长尾问题,真实数据里用户根本不问那些偶发情况,等出了事故再去补数据,黄花菜都凉了。
1.1 合成数据的核心价值
合成数据就是在这种情况下被推上台面的。
我用个类比:真实数据是"实拍素材",合成数据是"摄影棚里搭景"。实拍素材当然最真实,但你没法控制天气、光线、演员的每一个动作;搭景虽然要花功夫,但一切要素都可控,可以按需反复拍,想拍多少拍多少。NeMo Data Designer干的就是搭景这件事:给定一小批高质量种子数据,由它基于模板和语言模型自动派生出一大批形态相近、但内容不同的新样本。
合成数据三个最实在的价值:
第一条是规模化。几百条种子数据可以稳定扩展成几万条,且扩展速度远远快于人工标注。
第二条是可控性。数据的难度、长度、领域、说话风格都可以在设计模板时预先设定,想要更多长文本样本就调长文本模板的占比,想要更多反问句式就加反问式模板。
第三条是隐私友好。因为生成过程不依赖真实用户对话原文,很多时候可以做到"数据脱敏"的效果。
当然,合成数据也有自己的问题,比如多样性不足、重复率高、错误信息被"一本正经"地生成出来。这也是为什么我不能只讲生成,后面专门用一章讲质量控制和过滤,这一步才是把"大量数据"变成"高质量数据"的关键。
1.2 NeMo Data Designer 在这个环节里扮演什么角色
NeMo Data Designer 是一套面向数据集设计与生成的工具,核心思路是把"数据生产"拆成几个可编排的环节:先定义数据长什么样(Schema),再定义内容从哪来(模板与种子),然后通过批量执行生成任务(Generator),最后对产物做校验和过滤(Filter)。
这套设计最大的好处是流程可复用。我这次做知识库问答,定义好的Schema和模板,后面换到另一个业务线,只需要替换种子文件和模板描述,不用重写流程。另外它对并行生成的支持也比较省心,多路并发跑起来之后,基本就是等着看结果。
需要说明的是,工具本身不解决"你想生成什么"的问题——想生成客服对话、想生成摘要样本、想生成多选题,这些都取决于你给它什么样的Schema和模板。工具的价值在于让"生成-校验-筛选-导出"这条流水线变得顺手、可靠,把原来要在好几个脚本之间来回倒腾的活,收敛到一个可控的项目里。
2. 环境准备与项目初始化:把NeMo Data Designer跑起来的完整流程
工具再好,跑不起来都是白搭。我这次是从零开始装的,踩了一些环境上的坑,把流程梳理出来供参考。
2.1 安装与环境依赖
先说一下我用的环境:一台Linux机器,内存32G,有一块显存16G的GPU,存储盘留了200G给数据文件。如果你没有GPU,只做中小规模的数据生成,CPU模式也能跑,就是慢一些,并发和batch要调小。
安装依赖这一步,建议用一个干净的Python环境来操作,装一个虚拟环境,把Python版本固定在一个较新的稳定版本上,避免系统自带的旧版本Python带来一堆兼容性问题。工具依赖的库比较多,包括数据处理、schema校验、文件IO相关的几个常见库,都可以通过包管理器一次性拉下来。
安装过程中我遇到一个比较典型的问题:直接安装最新版之后,和本机已有的一个老版本库产生了冲突,表现为运行示例脚本时报类型错误。排查了一圈,发现是依赖版本冲突,不是代码问题。后来我在环境文件里把几个核心依赖的版本范围固定下来,重新装一遍就正常了。
2.2 创建项目与目录结构
装好之后是创建项目。NeMo Data Designer的项目结构很清晰,我建议按它默认的目录去建,不要自己乱改,因为后面批量生成时它会按约定去读文件和写结果。
我的目录大致是这个样子:
data_project/ ├── configs/ # 项目配置文件 ├── schemas/ # 数据Schema定义 ├── templates/ # 生成模板与种子样本 ├── generated/ # 生成结果输出目录 ├── filtered/ # 过滤后结果输出目录 └── logs/ # 运行日志新手最容易忽略的是configs目录里的配置文件。生成并发数、batch大小、输出格式、随机种子等参数都在这里控制。随机种子这个东西容易被遗忘,但如果你想复现同一批数据,种子必须固定下来,不然每次生成的结果都不一样。
创建好项目之后,第一步建议先用官方示例跑一遍全流程,确认每一步都通。我自己的习惯是先配置一个小规模的任务,比如只生成50条样本,跑通后再放大到目标规模。别一上来就开5万条的任务,万一中间配置有问题,一次性任务跑几小时才发现错误,时间成本太高。
3. 核心实战:从数据Schema到批量生成的高效流水线
接下来是重头戏,怎么把一批种子数据扩成大规模合成数据集。我以"企业内部知识库问答"为例,完整走一遍流程。
3.1 定义数据Schema:先想好生成结果长什么样
很多人在做合成数据时容易犯同一个毛病:还没想清楚输出格式,就急着写模板调模型。结果生成回来的数据结构五花八门,清洗环节痛不欲生。
Schema就是提前约定输出结构。我的问答数据Schema大概长这样:
schema: name: "knowledge_base_qa" fields: - name: "question" type: "string" required: true description: "用户问题,应为一个完整的问句" - name: "answer" type: "string" required: true description: "基于知识库内容的回答,需包含关键信息" - name: "intent" type: "string" required: false description: "问题意图分类,属于枚举列表中的值" - name: "topic" type: "string" required: true description: "所属知识点主题"定义字段时我建议把description写清楚。这句话看起来很不起眼,但它是后续生成时的约束条件。模型或者规则引擎会参照这段描述来判断什么是合格输出,描述写得模糊,出来的数据质量就飘。
还有一个容易被忽略的点:字段的required标记一定要想清楚。我一开始把answer字段标成optional,因为觉得某些场景可能没有标准答案。结果生成的样本里混进来一堆只有问题没有答案的残缺数据,后面清洗时只能整批扔掉。设计上偷懒,清洗时就加倍还回来。
3.2 设计生成模板与种子数据
Schema定好了,下一步是准备模板和种子数据。
模板的作用是告诉生成器"问题应该怎么问、回答应该怎么答"。比如我的模板里会写:
请根据以下知识库条目生成一个问答对。 知识库条目:{knowledge_entry} 要求: 1. 问题必须是用户在真实场景中可能会问出的自然问句; 2. 回答必须基于知识库内容,不得添加知识库中不存在的信息; 3. 回答长度控制在50-200字之间; 4. 如果知识库条目包含具体操作步骤,请将操作步骤完整保留在回答中。这里有一个关键技巧:模板里用占位符(比如{knowledge_entry})来注入变化的内容。生成器每次运行时,会从种子库里抽取不同的知识条目填入占位符,这样每条生成结果都是不同的。
种子的质量直接决定产出质量。我这次用的种子知识条目是从原始资料里精挑细选的,大约200条,覆盖了高频咨询、操作流程、政策说明、异常处理四个类别。每条都人工审过,确保没有错误信息和过时内容。这一步别省,种子烂了后面生成出来的东西只会更烂,而且问题会被放大到几万条。
3.3 配置生成策略并执行批量任务
模板和种子就绪后,开始配置生成策略。
需要关注的几个参数:batch size、并发数、输出路径。以我的项目为例,我用的配置大致是:
generation: strategy: "llm_based" model: "local_llm" batch_size: 16 concurrency: 8 max_tokens: 512 temperature: 0.7 top_p: 0.95 seed: 42 output_format: "jsonl"temperature是最影响数据多样性的参数,我个人习惯放在0.7附近,太低会生成大量几乎一模一样的句子,太高则容易跑偏,输出一些逻辑不通的内容。如果某批生成结果看着太统一,先检查一下temperature是不是设得太低。
batch size和并发数要结合显存来定。16G显存跑16的batch还好,如果改成64就会出现OOM。建议从小往大调,稳定跑通之后再逐步增加。
执行批量任务时,强烈建议开启日志记录。生成过程中会有很多信息,比如任务进度、失败样本数、每条样本的耗时。这些信息不只是用来监控,更可以用来判断当前配置是否合理。
跑起来之后不要干等。我习惯每隔一段时间去翻一下生成的中间结果,随机看几十条。如果发现跑偏,随时停下来调整参数重新跑,总比5万条全生成完再返工省时间。
4. 质量控制与数据多样性:让合成数据真正可用
生成只是一半,另一半是筛。很多人在这一步没耐心,结果模型训练出来效果差,最后把锅甩给合成数据。实际上问题往往出在"只生成、不质检"。
4.1 格式校验与字段一致性检查
生成完成以后,第一步就是格式校验。我这次用的输出格式是jsonl,每一行都应该是一个结构完整的JSON对象。用脚本做一道基础校验,把解析失败的行单独捞出来。
格式校验的脚本逻辑很简单:
import json def validate_line(line): try: obj = json.loads(line) except json.JSONDecodeError: return None for field in ["question", "answer", "topic"]: if not obj.get(field): return None return obj跑完这道校验,能筛掉一批因为输出截断或JSON转义错误导致的坏数据。这类数据占比一般不高,但如果不筛,后面训模型时就是一颗颗雷。
字段一致性检查则是看内容是否符合Schema里的约定。比如intent字段,如果Schema里定义了枚举值,就要检查生成结果里的值是否都落在枚举范围内。落到范围外的,要么是模型自由发挥造出了新类别,要么是模板里的描述不够严格。
4.2 去重与相似度过滤
合成数据最大的隐患是重复率。用模板+LLM的方式生成,即使固定了random seed,同一批数据里依然会出现大量语义相同、措辞略有不同的样本。这些样本对模型训练来说,信息量很低,白白占用了训练预算。
去重的思路分两层。第一层是精确去重,直接去掉完全相同的字符串。第二层是近似去重,用文本向量的余弦相似度来判断两条样本是否过于接近。我在项目里把阈值设为0.85,高于这个值的样本直接视为重复。
近似去重这一步要注意一个细节:不要把同一topic下的样本误删。因为同一个知识条目生成的问答对,语义上天然相近,如果阈值设得太低,会把本来应该保留的多样性样本也给滤掉。建议先用一批样本来测试阈值,观察误删率,再定最终的参数。
4.3 人工抽检与迭代反馈
自动化筛选做完,最后还有一道人工抽检。我一直坚持这个环节不能省,原因很简单:规则和相似度阈值解决不了语义层面的错误。
抽检的做法是:按1%-2%的比例随机抽取生成结果,逐条人工阅读,记录错误类型和占比。我这次抽检了500条,发现的问题主要集中在三类:
一类是知识幻觉,个别回答添加了知识库中不存在的细节,例如某个流程不该有的前置条件。一类是意图偏移,生成的问题看起来自然,但跟对应知识点的关系不够紧密。还有一类是模板痕迹太重,读起来明显是机器生成的固定句式,缺少真实场景里的口语感。
这些反馈不能看完就算,要拿回到生成环节去调。比如发现知识幻觉比较多,就去强化模板里的约束描述,明确"不得添加知识库中不存在的信息";发现模板痕迹重,就调整temperature或者在模板里增加更多样化的句式和示例。
我实际迭代了三轮:第一轮跑出来2万条,抽检发现幻觉率偏高;调整模板后第二轮再跑2万条,问题明显减少;第三轮跑完剩下的1万条,汇总抽检合格率已经能接受。整个调优过程看似繁琐,但值得做。
5. 常见问题与避坑实录:我们实际踩过的那些坑
最后分享几个我实际遇到的典型问题和处理过程,有些是查了半天资料才弄明白的。
5.1 生成结果里混入未替换的占位符
这个坑非常隐蔽。一开始跑出来的数据里,偶尔会出现一行包括{knowledge_entry}这类字样的文本。原因是模板解析时,部分条目的占位符没有命中种子数据,生成器直接原样输出了。
排查方式是在格式校验之外再增加一道占位符检查,扫描所有生成文本,只要出现过{和}包围的占位符字符串就标记为异常。这个问题不大,但影响很致命,因为这种数据混入训练集后,模型可能会学会在输出中保留占位符格式。
5.2 输出文本被截断导致JSON解析失败
LLM生成时设置了max_tokens,回答较长时容易在JSON结束符之前被截断,导致一整行解析失败。我遇到过一次比较严重的情况,单批任务的失败率接近10%。
解决思路有两个方向:一是调大max_tokens,二是把JSON输出改成逐字段输出,最后再组装。我最后采用的是方案二,让回答字段单独生成,然后由后处理脚本统一封装成JSON行,这样即使回答被截断,也不会影响整行数据的结构。
5.3 多样性不足,生成的样本高度趋同
有一批任务生成出来的结果风格极其统一,句子结构几乎都长一个样。最初还以为是模板写得太死,检查下来发现是temperature设得太低,默认值0.1的情况下模型基本在走概率最高的路径。
调整到0.7之后,多样性有了明显改善。另外在模板中加入了多种句式提示词(如"以另一种方式表达同样的内容"),也起了一定的作用。多样性问题需要多管齐下,单一参数往往解决不了。
5.4 幻觉数据:模型一本正经地编造"事实"
这个是最难处理的一类。因为它的生成结果看起来完全合理,词汇通顺、格式规范,但内容本身是错的。对于知识库问答场景,使用包含错误信息的样本训练模型,风险比数据稀疏更大。
我的应对手段是三层保障:
第一层,在模板中明确约束"只能使用知识库条目中的信息"。
第二层,在生成后加一道基于知识库的自动比对,抽取回答里的关键实体和数字,与知识条目做匹配校验,发现疑似新增信息就标记为存疑。
第三层,对存疑样本走人工复核,能修则修,不能修的直接删除。
这套流程不一定能完全消除幻觉,但可以把比例压到可接受范围。
我把几个高频问题整理成了一个速查表,大家可以对照参考:
| 问题现象 | 可能原因 | 处理建议 |
|---|---|---|
| 占位符未替换 | 种子数据未覆盖模板变量 | 增加占位符后处理检查 |
| JSON解析失败 | max_tokens太小或输出被截断 | 调大上限或改为逐字段生成 |
| 样本高度趋同 | temperature过低 | 调至0.6-0.8,增加句式模板 |
| 生成内容出错 | 模板约束不足 | 强化描述,增加知识比对 |
| 字段缺失 | Schema中required标记不准 | 严格required并增加校验 |
| 运行时报类型错误 | 依赖版本冲突 | 固定版本范围重装环境 |
5.5 一个值得养成的习惯:全流程版本化管理
这个不能说是一个坑,更多是吃过亏之后的习惯。数据生成项目和写代码类似,模板文件、Schema、种子数据、配置参数,每次改动都应该留下版本记录。否则调优三轮之后,你会发现根本说不清哪些数据是哪一版配置生成的。
我的做法是把每次运行的配置文件和模板目录做一个快照,放在以时间戳命名的子目录下。这样既能复现,也能随时对比不同版本的数据质量差异。数据链路越往后越复杂,管理不过来的时候,这个习惯能帮大忙。
写到这里,我这次用NeMo Data Designer构建合成数据集的完整过程就基本梳理完了。个人最大的体会是:合成数据这项技术最考验人的不是工具操作,而是对数据质量的敏感度。工具能帮你把生成、校验、过滤的流程自动化,但在什么时候调模板、什么时候改阈值、怎么判断一批数据能不能用,这些判断力只能从一次次抽检和复盘里积累出来。
最后再分享一个小技巧:不要一次性把全部资源压在最终版本上。先小批量、多轮次地跑通流程,确认每一环的输出都符合预期之后,再放开手脚做大规模生成。前期的试错成本很低,但能帮你省下后期返工的大把时间。如果你正准备做合成数据集,希望这篇实战记录能让你少走一点弯路。