1. 从听得见到听得懂这套系统的定位作为一款主打自然语言驱动的语音交互系统我们的目标从来不是做一个能听写的录音笔而是让用户用一句话就能完成一个完整的操作闭环——说把客厅空调调到26度系统不只是识别出这串文字还要真正驱动空调执行动作。这个从语音到动作的链路才是自然语言驱动与传统语音识别的本质区别。我参与这套系统开发时团队内部拉齐过一个共识语音识别只是耳朵自然语言理解才是大脑。耳朵负责把声波转成文本大脑负责弄清楚用户到底想干什么然后交给执行层去干活。如果只做识别不做理解产品就是个高级听写工具用户必须自己从文字里找重点再一次次点按操作谈不上驱动。反过来如果只做语义理解而没有足够的识别精度大脑再聪明也架不住耳朵听岔了。所以整套系统的功能说明业内更准确的叫法是语音交互中间件向上承接用户的语音输入向下对接业务系统的执行接口。它对外暴露三件事——识别ASR、理解NLU、驱动Action。这三件事串起来就是一次完整的自然语言驱动调用。这套东西适合谁参考我觉得两类人最需要一类是做智能硬件、想给设备加语音控制能力的产品经理和嵌入式工程师另一类是做软件服务、想集成语音入口的移动端或Web端开发者。前者关心离线识别和指令映射怎么做后者关心云端识别和多轮对话怎么接。我下面把整条链路拆开讲顺便把我踩过的坑也一并交代。2. 语音识别链路拆解声学、语言模型与解码器2.1 前端信号处理先让收音质量过关做语音识别的人都知道一句话识别效果的上限取决于前端信号处理的质量。麦克风采进来的原始音频如果不做处理直接丢给识别引擎哪怕引擎用的是最先进的模型也会在噪声、回声、削波面前翻车。我们在这块做了三个层面的处理。第一是端点检测VAD也就是判断人什么时候开始说话、什么时候说完。别小看这一步它决定了识别引擎的运算资源什么时候开启。我们用的是能量阈值加过零率双判据规则简单但很稳短时能量超过背景噪声均值的6倍、且过零率连续3帧超过设定值才判定为语音起始。这样能避免空调压缩机启动、门铃响这类突发噪声误触发。第二是回声消除AEC。因为设备自己会放提示音语音播放时麦克风会把喇叭里的声音采进来导致自己听见自己。我们用自适应滤波的方式做回声路径估计参考信号直接取播放通道的音频流在硬件上延迟控制在10毫秒以内否则参考信号和麦克风信号时间对不齐滤波器怎么调都白搭。第三是降噪。移动设备场景下比较头疼的是风噪和键盘敲击声。风噪属于低频稳态噪声我们用了谱减法做初步衰减再配合统计模型估计噪声谱瞬态噪声比如键盘声、关门声则依靠多频带子带降噪去压。实测下来信噪比在15dB以上的环境识别率能稳定在95%以上到5dB的嘈杂环境也能保住80%左右。2.2 识别引擎流式与离线的取舍识别引擎这块我们最终选择了流式识别为主、离线命令词为辅的双轨方案。流式识别的优势是延迟低边说边出结果用户话音刚落就能拿到完整文本交互感好缺点是依赖网络万一断网就抓瞎。离线命令词识别则通过内置的小型语法网络固定识别几十个控制指令比如开灯、关灯、调高温度、下一首这种固定说法无网状态下也能救急。流式引擎我推荐用一个叫增量解码的架构思路音频帧按100毫秒的间隔送入模型解码器在已经在解码的部分基础上继续扩展而不是每来一帧就从头解一遍。这样做的计算量节省非常明显实测同一条语音全量解码需要300多毫秒增量解码可以压到150毫秒以内。这里有个容易被忽略的细节流式识别的结果需要在稳定点stable endpoint做提交而不是每个中间结果都去触发下游动作。我们会维护一个词级缓存只有连续两帧识别出来的文本完全一致才把这段文本标记为稳定并触发语义解析。这能极大减少因为识别抖动导致的误指令——很多人吐槽语音助手乱执行一半的锅其实在这。2.3 多语种识别模块的加入多语种支持是在项目进行到第二个版本时被用户需求倒逼出来的。出口设备要支持英语、日语后来又有用户反馈说家里老人只会说方言——于是又加了粤语和四川话的识别包。多语种识别最头疼的不是模型本身而是资源怎么分。我们的做法是维护一个语种矩阵同一套声学模型框架按语种分别训练声学模型语言模型则按语种定制中文用词粒度建模英文用子词建模日文需要在假名和汉字之间做转换层。如果一套模型强行覆盖所有语种准确率会互相拖累实测中英文混合的识别错误率会从8%飙升到20%以上。语种间切换我们引入了语种评分器在语音起始的前500毫秒先用一个轻量语种分类器判断当前语音属于哪个语种再把后续音频路由给对应的识别引擎。这个分类器用MFCC特征加一个小型CNN就可以做参数量不到50万推理一次只需20毫秒在设备端完全跑得动。3. 自然语言驱动语义理解层才是大脑3.1 意图识别与槽位填充从说了什么到要干什么识别引擎把语音变成文本之后系统才正式开始自然语言驱动的核心工作——把用户一句口语化的话映射成结构化指令。这个环节分成两步意图识别intent detection和槽位填充slot filling。意图识别回答的问题是用户想干什么。我们定义了8个顶层意图设备控制、查询应答、导航命令、媒体播放、提醒设置、电话拨打、系统设置、闲聊兜底。每个顶层意图下面又有若干子意图比如设备控制下面细分到调节温度、开关灯光、调整音量、切换模式。槽位填充则是把关键信息抠出来。比如把客厅空调调到26度这句话意图是调节温度槽位包括位置客厅设备空调目标温度26度动作语义调高/调低这里是绝对值设定这两个任务我们一开始尝试了分开建模意图用文本分类模型槽位用序列标注模型但发现两个模型独立推理会带来累积错误——意图分错了槽位再准也没用。后来改成联合建模在输出层同时预测意图标签和每个词的槽位标签共享底层的BERT编码层。联合模型的F1值比分开建模高出了近5个百分点推理耗时还少了一半。3.2 指令映射与执行引擎NLU到Action的最后一公里语义理解不是终点驱动才是。NLU模块给出结构化的指令intent slots之后需要一个执行引擎把它翻译成具体设备能理解的指令协议。比如NLU输出的温度26、位置客厅执行引擎要拼装成空调设备控制协议里的set_temperature_zone:living_room:26这样的下发电文。这个执行层必须做两件容易忽视的事。第一是参数校验26度在空调支持范围16~30度之内合法可以直接下发如果用户说调到100度执行引擎必须判定参数非法并回抛给NLU层触发生成澄清话术而不是把非法指令发给硬件。第二是设备状态核对用户说打开客厅空调如果客厅空调本来就是开着的执行引擎应该返回空调已经是开启状态而不是重复下发一条开机电文造成设备抖动。我见过不少项目在这里偷懒NLU解析完直接透传设备导致一连串产品事故。执行引擎是系统的最后一道质检线不接受任何语义层的瑕疵语义必须返回明确的三种结果执行成功、执行失败带原因、需要澄清带歧义点。只有这三种状态能反馈给上层做话术生成这样才能保证用户感受到的对话是流畅、有逻辑的。3.3 多轮对话的上下文管理自然语言驱动区别于单条指令的一个关键点是多轮对话的上下文管理。用户一句话有时候不是自包含的比如用户把空调打开系统好的已打开用户调到26度第二句调到26度没有出现空调和客厅这两个槽位但如果系统不做上下文继承这条指令就缺失了设备对象无法执行。我们用一个上下文管理器来维护当前焦点设备和历史槽位。具体实现上每一轮语义解析完成后上下文管理器会做三件事把本轮已确认的槽位存进会话状态session state把当前对话焦点比如用户正在操控的设备ID标记出来对下一轮解析出来的不完整指令做槽位回填——调到26度在解析出温度26之后自动从上下文里补上设备空调、位置客厅。这里有个产品层面的原则上下文继承不做无限叠加最多继承前两轮的信息而且一旦用户切换了话题意图发生变化旧槽位全部清空。之所以这么设计是因为实际使用中用户连续三句话都不补全设备名的场景极少更多情况是打开空调——调高一点——还是太冷——那关了吧这种快速连续的调整。继承太多反而容易把用户上一轮已经放弃的设定误带进来造成我明明说的客厅你怎么开的卧室这种事故。4. 多语种识别落地的实际细节4.1 语种识别器与主识别器的时序配合前面提到我们用一个轻量的语种分类器在语音起始阶段判断语种然后把音频路由到对应的识别引擎。这个设计听起来简单实际落地有几个坑。第一个坑是语种判断窗口和说话起始没法完全对齐。用户说Open the window开头的O音能量很小语种分类器在500毫秒的窗口里可能只收到半个音节判断置信度不够。我们的对策是语种分类器输出的是概率向量中文、英文、日文、粤语等不硬选只有最高概率超过0.7才做路由否则走主语种识别引擎默认为中文等识别结果出来之后再用整句级的语种评分做二次校验。简单说就是先按默认语种跑中途发现不对再切换。第二个坑是双语混说。广东用户经常会说开一下冷气set to 24吧一句话里中英文交替。全句部署成一个语种引擎必然有一段被识别得很差。我们的做法是支持句内混语句子先按语种scorer在句级分类出主导语种这个例子是中文识别时中文引擎为主但词表里预置了常用英文词汇set、mode、channel这些在解码图里允许中英文词任意跳转。这样混语场景的识别率能从40%拉到75%左右虽然比纯中文低但在实际场景里已经可用了。4.2 多语种资源包的工程化组织多语种绝不是简单塞几个语言包进去就能用的工程化上要做几件事。我们的做法是按语种独立管理声学模型每个语种一个目录模型文件按版本号命名支持热更新。语言模型词表按语种拆分中文词表约50万词英文用BPE子词表约10万词日文走假名转写汉字表映射。语种路由配置做成一张表格映射语种代码、识别引擎地址、热词表ID、置信度阈值运维人员可以直接改配置下发不用动代码。热词表在多语种系统里至关重要。同一个英文词play在play music媒体和play ground地点里权重完全不同。我们针对每个语种维护了领域热词表——智能家居场景下的英文热词有air conditioner、temperature、dim等音视频场景有play、volume、next、pause。热词表会在语言模型解码时做偏置让包含热词的路径得分更高。这个简单手段效果立竿见影领域内词汇识别错误率能降低30%以上。4.3 语种自动切换还是手动指定这其实是个产品决策不单是技术问题。我们调研了一圈竞品发现两种路线都存在手动选语种好处是识别稳定但用户切换麻烦自动切换体验好但误判语种时体验更糟——用户说了中文系统按英文引擎识别出来的文本文不对题然后再触发一次重识别延迟翻倍。我们的折中方案是自动识别手动指定兜底。语音入口默认走自动语种识别但当用户在系统设置里手动指定了仅中文或者仅英文时语种路由直接锁定不再走分类器。实测反馈显示有大约20%的用户会选择固定语种他们通常是单语种使用者对识别速度更敏感不想要自动切换带来的那一两百毫秒额外开销。同时对于自动切换模式还要做好语种记忆某个用户连续五次交互都是中文系统就把这个会话的语种权重向上调中文分类器的判决阈值从0.7降到0.55降低说法语种跳来跳去的概率。这个机制我们内部叫说话人口音适配本质上是会话级的语种先验。5. 典型场景实测这些数据是我们的真实成绩5.1 智能家居控制场景一句话闭环整套系统最初的核心场景就是智能家居语音控制。我们在一套模拟房间里部署了6类设备空调、灯、窗帘、电视、加湿器、扫地机器人评估指标是一句话任务成功率——用户说一条自然语言指令系统在10秒内完成识别、理解、下发给设备设备状态正确变更。测试指令集覆盖了1000条真实用户录制的语音做了三个维度的统计场景变体任务成功率平均端到端延迟主要失败原因无噪声环境、普通话96.8%1.8秒设备状态冲突家庭噪声背景、普通话91.2%2.1秒识别错误中度噪声背景、粤语84.5%2.3秒方言识别词表覆盖不足端到端延迟1.8秒的含义是用户说完话最后一个字的时刻到设备执行下发的时刻。这个数值在行业里算中上水平主要耗时分配是流式识别出最终文本约600毫秒、NLU推理约300毫秒、执行引擎拼装和下发电文约200毫秒剩下的都是音频缓冲和网络传输开销。要再往下压瓶颈在网络不在算法。5.2 多语种场景的混合指令实测多语种识别模块上线后我们专门组织了一次三语混聊测试测试者在一个会话里先后用中文、英文、日文对系统发布指令要求系统全程无感切换。测试结果里有意思的是语种切换的成功率并不是按每一次切换都成功来衡量的因为语种分类器偶尔会犯错。我们用的是最终任务成功率——哪怕切换那一下中间出错了只要后续重新识别能把这个任务完成就算成功。这个口径下中英切换任务成功率93%中日切换89%中英日三语轮换82%。让我说一个比较典型的失败案例测试者用英文说了Turn on the living room light但living room发音偏快、带吞音英文引擎把这段识别成了living loom light语义层在槽位填充阶段无法匹配living loom到任何已知位置于是触发了澄清机制系统回复Sorry, which light did you mean?用户在第二次交互中补了the one in the living room才最终执行成功。这种识别小错澄清兜底的链路在真实多语种场景里非常重要如果系统直接按错误槽位执行后果就是开错灯。5.3 白天实际使用中的隐性指标除了任务成功率我们还统计了几个容易被人忽略的指标对产品体验影响极大。第一个是误唤醒率。系统默认唤醒词是小听小听在20平米测试间里模拟电视声、谈话声、厨房炒菜声统计24小时内没叫唤醒词时系统被误唤醒的次数。我们做到的是低于2次/天代价是唤醒词上做了两遍校验——本地一遍轻量模型云端一遍大模型复核本地通过才送云端。误唤醒率压不下来用户会被烦到直接关掉设备电源这个功能等于白做。第二个是首句时延也就是从唤醒到系统给出第一次反馈的耗时。这个数值用户感知最强。我们压到了800毫秒以内主要做法是预热唤醒成功后立即预加载声学模型和NLU模型到内存不等用户开始说话再加载。模型加载这种耗时Шаг不能等到请求到了才做必须提前完成。第三个是二次纠错成功率。语音交互免不了第一次识别出错用户会重复一遍。我们统计到大约15%的用户指令需要纠错。关键在于用户重复时往往会在句首加一个不是或我说的是——这些词不能被当成指令内容送进识别器。我们的VAD模块识别到一个加重语气且与上文语义冲突的短句时会触发重听模式自动丢弃该短句并重新开启收音。这个细节对体验提升帮助很大纠错场景的用户满意度从62%上升到81%。6. 落地过程中踩过的坑6.1 识别率不低用户为什么还是觉得笨刚上线时我们的普通话识别准确率已经做到97%了实验室安静环境但用户反馈依然大量吐槽这助手太蠢。后来我们复盘用户录音发现真正的拦路虎是口语省略和指代。用户不会说规范指令他们说的是那个灯太亮了——没有指名是客厅灯还是卧室灯就一个那个。语义层拿到这句槽位缺失严重那个是泛指还是指代当前场景里的哪个设备单句根本无从判断。这就是我在3.3节说的上下文管理器真正发挥作用的地方它知道用户三秒之前刚刚操作过客厅灯于是把那个灯关联到客厅灯。我们在1.2版本补全了完整的指代消解规则才把这类场景的成功率从55%拉到了88%。所以做自然语言驱动不能只盯着识别准确率一个指标要盯着用户真实指令的完成率。用户不在乎你识别出几个字只在乎你听懂了他要什么。6.2 多语种切换的第一耳朵问题多语种自动识别上线后我们收到一个很有意思的反馈用户在中文模式下说了一句很长的英文句子系统前500毫秒按中文引擎识别吐出来的几个字明显是乱的然后又切成英文引擎重新解码最终文本是正确的但中间那段乱码已经在界面上显示出来了。这种先出错的中间结果显示出来再被修正的现象对用户的信任打击非常大。后来我们做了两个改动一是流式的中间结果不直接展示给用户而是缓存到内部缓冲区只有稳定点之后的内容才显示二是在检测到语种切换时清空缓冲区让界面能感知到系统正在重新理解。这个坑的根源是语种识别需要时间但流式识别结果不能停。做完改动以后多语种会话的用户满意度提升了约12个百分点。我建议所有做多语种交互的项目都把中间结果可见性当成一个正式的产品体验指标来设计。6.3 命名实体冷启动领域词表的长期运营最后这个坑可能所有语音项目都会遇到领域自定义词表的冷启动。我们一开始给英文引擎只配了通用词表结果发现设备名、房间名这类专有名词识别率奇低——用户说打开墨菲房间的灯墨菲是人名英文引擎直接乱识别成打开莫非房间的灯。解决思路不是靠模型而是靠热词表运营。我们做了一个每周更新的流程从用户日志里捞高频识别错误词人工校验后放进对应语种的热词表每周五发一版更新包。上线三个月后自定义设备名和房间名的识别准确率从61%涨到了89%。这里也有个注意点热词表不能无限膨胀每加一个词都会轻微拉低整体解码性能。我们设置的上限是每个语种1.2万个热词超过就会触发优先级评估把三个月内没有被任何用户命中的热词淘汰掉。机制听起来简单但在真实系统里它就是靠这种持续的小步迭代一点点逼近用户预期的。对我来说做这套系统的最大体会是自然语言驱动和语音识别真正难的地方不在那些论文里的SOTA指标而在那些用户说了一句你完全没想到的话的时刻。数据、规则、边界条件、兜底机制每一个环节都得提前想好——因为用户不会按你写的测试用例说话。多语种更是把这种不确定性的维度又拉高了一截。如果你也在做类似的项目建议你在搭架构时就把上下文管理、执行引擎校验、热词运营这些看似边缘的模块纳入主干设计而不是等上线被用户教做人之后再补。
阅读完成 · 觉得有帮助?