· 阅读约 4 分钟
从源头修复语音识别
为什么一个用来清理乌尔都语转写垃圾的过滤器也删掉了真实的语音,以及为什么修复糟糕转写的关键在于语音智能体如何“听”,而不在于它事后如何处理文本。
机器翻译。 阅读原文
VoxRange 运行的 AI 智能体负责接听真实的电话,使用乌尔都语,以及人们实际说话时那种乌尔都语与英语夹杂切换的混合语言。我是它的首席架构师。这篇文章讲的是我们在这条流水线“听”的环节犯下的一个错误,以及我从中总结出的规则。
症状
在乌尔都语通话中,语音转文字总是在乌尔都语转写文本中间夹杂零散的拉丁字母文本。窄带电话音频、混合语言和人名本就让转写变得困难,而这些碎片让情况更糟:转写写的是什么,智能体就回应什么。
显而易见的做法是清理输出。如果智能体被设定为使用乌尔都语,而某个片段以拉丁字母返回,就在智能体看到之前把它丢掉。
为什么这个过滤器是错的
过滤器确实起了作用,垃圾文本消失了。但真实的语音也随之消失了。
来电者并不会只使用一种文字。大量乌尔都语会用拉丁字母书写为罗马化乌尔都语,而日常对话也经常为了一个词或整句话切换到英语。举一个虚构的例子:“mera appointment confirm kar dein”是一个再普通不过的请求,其中每个词都是用拉丁字母书写的真实语音。一条删除拉丁字母文本的规则无法把这句话和噪声区分开,因为在它运行时,唯一能用来判断的只剩下文字类型,而问题根本不在文字类型上。
我们撤回了这个过滤器,只保留了一个范围更窄的版本:在这些通话中不可能是真实语音的文字,例如西里尔字母或 CJK 字符,仍然会被丢弃,因为删除它们不会造成任何实际损失。
垃圾到底从何而来
这些零散碎片是“听”这一环节出现异常的症状,因此修复也应该放在那里:
- 语音结束检测。语音活动检测负责判断来电者何时停止说话。如果判断得太早,一个对话轮次就会被切成碎片,而一个词的碎片恰恰是语音模型最容易转成自信满满的胡言乱语的那种音频。
- 语言锚定。告诉模型应该预期哪种语言,而不是让它在每个短片段上自行猜测,可以防止它在一阵噪声中跑偏到另一种语言。
- 词汇表。模型从未见过的人名、地名和领域术语,会以近似但错误的猜测返回。把它将会听到的词汇提供给它,比事后修补这些猜测成本更低。
这些措施中的每一项都改变了模型听到或预期的内容。没有一项会丢弃来电者说过的任何话。
相关的教训:配置漂移
语音结束检测依赖一个值:在判定来电者说完之前要等待多长的静音。我们在之前的通话问题之后调过这个值。但在后来的一通电话里,它又回到了一个激进的值,智能体在来电者说到一半时就打断了对方。最初导致糟糕转写的那些碎片化轮次也随之回来了。
这个值只存在于某台服务器的环境变量中。代码及其历史记录里都没有保存调好的值,所以没有任何东西能阻止它被改动。像这样的调优值应该放在受版本控制的配置中,紧挨着读取它的代码,并附上说明为什么设成这个值:
# voice-runtime config, versioned with the code
# How long to wait in silence before ending the caller's turn.
# Too short cuts callers off and fragments transcripts.
END_OF_SPEECH_SILENCE_MS: <tuned value>同一通电话还让我们学到了第二点。一个轮次中的冷场时间是多个阶段的总和:等待语音结束、转写、检索、LLM 以及语音合成。其中一些阶段有计时,有一个没有。在一个轮次的每个阶段都被测量之前,你无法决定该优化什么,因为你没有测量的那个阶段,最终就是你只能靠猜的那个阶段。
给构建同类系统的人的建议
- 当模型产生垃圾输出时,先找到导致它的环节,再考虑写规则去删除它。输出过滤器看不到原因。
- 在信任任何清理规则之前,先用真实、杂乱的语音对它进行测试,包括语言夹杂切换的情况。
- 把调优值保存在受版本控制的配置中并附上理由,而不是只放在服务器的环境变量里。
- 在优化任何一个阶段之前,先测量一个轮次的每个阶段。