拿到 Qwen-Audio-3.1 的转写结果,先别急着写库:模型返回的通常是一段连续文本,标点、换行、说话人边界都需要在入库前用一条固定的清洗流程补上,否则下游检索定位不到句子,人读起来也断不开。判断这套流程有没有跑偏,不靠对模型输出的标点质量下结论,而是拿自己几段音频做原始输出与处理后文本的前后对照。
标点和分段建议当两步加工,顺序用自己样本对照后再定:通常先按静音区间和说话人切换切段,再在段内补标点,比先加标点再切分更稳。切分依据应来自时间戳或静音时长,而不是标点符号。入库字段先固定为音频标识、分段序号、起止时间、文本四项,配一条起止时间合理性校验;不通过的分段落入复检队列,不静默丢弃。
抽三段不同类型的录音看原始输出长什么样
清洗规则要照着原始返回的实际形态写,不能照想象写。建议先选三段:一段单人独白,一段两人以上交替说话,一段中间有明显静音或长时间无人声。这三类基本能覆盖标点缺失、说话人边界、空段这三类常见问题。
每段音频都要记录:返回体里有没有标点、是半角还是全角、有没有换行符、有没有说话人标记、时间戳给到句级还是字级、有没有单独的空片段或语气词片段、重复词是怎么保留的。记录方式是原样落盘,不要在这一步做任何改写:
raw/{audio_id}/response.json # 原始返回,原样保存
raw/{audio_id}/meta.yaml # 音频时长、采样率、声道、说话人数
原样留存的意义是后面每一步清洗都能回查。如果清洗脚本把原始返回也覆盖掉了,出现边界错误时就无法判断是切分规则的问题还是模型输出的问题。
把连续文本按停顿和说话人切换切成分段
整段连成一片的后果是检索命中的是整篇音频,定位不到具体位置。切分依据按优先级排:先看模型是否给了静音区间或时间戳,有就按静音时长阈值切;再看是否有说话人标记,说话人一变就断开;两条都没有时,才退回到“按最大时长或最大字数兜底切”,这种切法只能算占位,边界质量需要用样本单独核对。
segments = []
for utt in raw_units: # 按时间顺序遍历模型返回的片段
if utt.speaker != cur.speaker:
flush(cur); cur = new(utt)
elif utt.gap_ms >= GAP_MS: # 静音阈值,按自己样本调
flush(cur); cur = new(utt)
else:
cur.text += utt.text
if cur.dur_ms >= MAX_DUR_MS: # 超长兜底
flush(cur); cur = new(utt)
flush(cur)
切完以后抽样核对:每段音频抽五到十段,把处理后的起止时间对回原音频听一遍,确认切口落在一句话说完之后的停顿里,而不是落在词中间。抽到边界明显不对的,回头改阈值重跑,不要手工改单条。
标点处理放在分段前还是分段后各跑一遍做对照
两种顺序各跑一遍,用同一批样本对比,这是确定顺序最省事的办法。
- 顺序 A(先补标点再切分):原始文本 → 补标点 → 按标点与停顿切分。骨架是标点补全后再找句号、问号作为断点。
- 顺序 B(先切分再补标点):原始文本 → 按静音与说话人切分 → 只在段内补标点。骨架是切分只依赖时间戳,标点只影响段内可读性。
对照维度看三个:分段数量差多少、切口位置是否落在同一处停顿、读起来句子是否完整。顺序 A 的主要风险是补出来的标点会改变停顿判断,切口跟着标点跑;顺序 B 的切分不依赖标点,因此更稳定,代价是段内可能出现较长的无标点句。多数场景建议先按顺序 B 跑,若对照后发现段内可读性明显不足,再考虑在段内做标点补全,而不是把标点提前到切分之前。
确定入库字段并写一条校验规则
字段先取最小集合,够下游定位和播放即可:
- audio_id:原音频标识
- segment_index:分段序号,从 0 或 1 开始要统一
- start_ms / end_ms:相对音频开头的毫秒起止
- text:该段文本
一条可执行的校验规则:起止时间必须递增且段长不超过设定上限,文本不能为空。不通过的分段写入复检文件并保留原因码,不直接入库,也不静默丢弃。
def check(seg, max_ms):
if not (0 <= seg['start_ms'] < seg['end_ms']):
return 'time_order'
if seg['end_ms'] - seg['start_ms'] > max_ms:
return 'too_long'
if not seg['text'].strip():
return 'empty_text'
return None
把清洗步骤写成可重复执行的一次运行
换一批音频时要跑同一套规则,脚本入口大致如下:
python clean_asr.py `--input` raw/ `--output` out/ `--config` clean.yaml
clean.yaml 里放可调项:静音阈值、说话人边界开关、最大段长、标点处理顺序、是否保留语气词。输出位置建议分成三处,便于追溯:
- out/{audio_id}/segments.jsonl:通过校验的分段
- out/{audio_id}/dropped.jsonl:未通过校验的分段及原因码
- out/manifest.json:本次运行的输入文件、规则版本、时间戳
验证重复运行的稳定性,用同一批音频连跑两次,输出到两个目录后做目录级比对:
diff -r out_run1 out_run2
只要清洗过程里没有随机采样、没有依赖当前时间或字典遍历顺序,两次结果应当完全一致。出现差异时优先检查脚本里是否存在未固定顺序的遍历,而不是去调阈值。