语音对话合成数据工程:Schema、口语化与质量闸门

用大模型合成对话数据很容易,难的是让这批数据真正进入语音系统的训练和评测闭环。一个可用的语音对话样本,不只是几轮看起来顺畅的文本。它还要保留角色、话轮、时间、通道、实体槽位、ASR 噪声、口语现象和质量标记。

如果这些约束只写在 prompt 里,数据规模一大就会漂移:格式不稳定、标签不一致、口语化过度、实体边界模糊,最后训练出来的模型学到的不是交互能力,而是一批生成器的随机习惯。

先定义数据契约

合成数据的第一层不是 prompt,而是 schema。每条样本至少要回答几个问题:

  • 这句话属于哪个对话、哪个话轮、哪个角色;
  • 是否来自单通道还是多通道音频;
  • 是否有时间边界;
  • 文本是干净转写、ASR 候选、纠错结果还是标注结果;
  • 是否包含实体、意图、结束点或其他结构化标签;
  • 是否经过噪声注入、口语改写或人工复核。

一个通用的 JSONL 样本可以长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"key": "dialog-001-turn-003",
"dialog_id": "dialog-001",
"turn_id": 3,
"role": "user",
"channel": 1,
"start_ms": 4280,
"end_ms": 6910,
"txt": "我想查一下刚才那个信息",
"slots": [],
"history": [
{"role": "assistant", "txt": "您好,请问需要查询什么内容"}
],
"quality_flags": ["spoken_style", "schema_valid"]
}

字段名可以按项目调整,但契约要稳定。训练、评测、过滤、回溯都应该围绕这套契约运行,而不是每个脚本临时解释一版格式。

口语化必须受控

真实语音对话里有停顿、重复、修正、附和、打断、无效输入和噪声。但这些现象不能随机撒进文本里。

口语化最好拆成可控变量:

  • filler:语气词和停顿词;
  • repetition:自然重复和异常复读;
  • repair:说错后自我修正;
  • interruption:用户打断或系统抢话;
  • invalid input:沉默、超时、无意义片段;
  • background event:背景人声、环境噪声、外放音。

每个变量都应该有触发条件、比例、最大强度和质量标记。比如重复可以提升真实感,但如果不区分“自然确认”和“异常复读”,文本纠错或语义结束点模型会被带偏。口语化不是越多越好,它是数据分布的一部分。

N-best 不是随便造错字

ASR n-best 数据很适合训练文本纠错、重排序和上下文恢复模型。但 n-best 的候选不能只是随机替换字符。

更稳的做法是把 ASR 错误拆成几类:

  • substitution:同音、近音或领域词混淆;
  • deletion:短词、语气词或弱读成分缺失;
  • insertion:重复词、填充词或误触发热词;
  • segmentation drift:切分边界前后错位;
  • normalization drift:数字、英文、缩写和口语读法不一致。

每个候选都应该保留错误类型和生成原因。这样后续模型评估时,才能知道它是在纠正同音错误,还是只是在把所有口语文本改得更书面。

历史上下文要筛选

对话模型经常需要 history,但把完整历史都塞进去会带来两个问题:上下文预算浪费,以及弱相关历史污染当前判断。

可以把 history 处理成三层:

  1. 固定窗口:保留最近若干轮;
  2. 相似度过滤:只保留与当前文本相关的历史片段;
  3. 结构化摘要:保留已确认实体、未完成意图和关键约束。

这里的重点不是“history 越多越好”,而是当前样本需要什么信息。对于 ASR 纠错,历史可以帮助恢复实体和上下文;对于语义结束点判断,过长历史可能反而让模型忽视当前话轮是否已经完整。

质量闸门要前置

合成数据进入训练前,至少需要四类检查。

第一是 schema 检查。字段是否齐全,枚举值是否在白名单内,时间边界是否单调,角色和通道是否一致。

第二是文本检查。空文本、异常重复、控制 token 泄漏、过长样本、标点风格混乱、数字规范不一致,都应该在这一层处理。

第三是标签检查。实体 span 是否能在文本中找到,结束点标签是否互斥,意图标签是否与角色匹配,噪声标签是否和样本内容一致。

第四是分布检查。口语化比例、错误类型比例、场景覆盖、长度分布、角色占比、无效输入占比,都要按批次输出报告。

如果只抽几条样本肉眼看,合成数据很容易显得“质量不错”。真正的问题通常出现在分布层:某类标签被生成器过度使用,某类错误完全缺失,或者格式通过率随批次下降。

训练集和评测集要分离设计

合成数据可以扩充训练集,但评测集不能照搬同一个生成策略。否则模型可能只是在适应生成器风格。

评测集应该额外保留:

  • 人工或高置信 gold subset;
  • 未参与 prompt 示例的模板和表达;
  • 更真实的 ASR 错误分布;
  • 边界样本和负样本;
  • 按错误类型拆开的指标。

端到端准确率只是最终结果。更有用的是知道模型在哪类样本上失败:是实体召回不够、历史过滤错误、n-best 候选没有正确答案,还是口语化扰动超出了训练分布。

可复用的流水线

一个可维护的合成数据流水线可以拆成七步:

  1. 定义 schema、标签枚举和质量标记;
  2. 生成干净对话骨架;
  3. 注入口语现象和交互事件;
  4. 生成 ASR-like n-best 或噪声文本;
  5. 做 schema、文本和标签校验;
  6. 输出批次质量报告;
  7. 抽样复核并回写 reason code。

这里最重要的是最后一步。不要只保留“通过/不通过”,而要保留原因:格式错、标签冲突、重复异常、历史不相关、实体不可定位、噪声过强。reason code 会成为下一轮 prompt、规则和模型改进的依据。

小结

合成语音对话数据的核心不是让大模型多写几轮对话,而是把生成过程变成可约束、可审计、可复现的数据工程。

schema 决定数据能不能被下游稳定消费;受控口语化决定数据是否接近真实语音;ASR-like 噪声决定纠错和重排序任务是否有训练价值;质量闸门决定扩量是否会变成污染。

真正值得沉淀的不是某个 prompt,而是一套能持续回答这些问题的流水线:这条数据为什么生成、改动了什么、保留了什么标签、通过了哪些检查、失败时应该归因到哪里。