ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

方言识别系统架构演进:从分离式到统一共享的工程实践

方言识别系统架构演进:从分离式到统一共享的工程实践 1. 背景方言系统为什么要做架构演进1.1 分离式架构的起点我最早接触方言系统是在一个语音客服项目里。当时业务方提的需求很简单用户说普通话能识别说方言也能识别。于是技术团队按最自然的思路把“普通话识别”和“方言识别”拆成了两个独立子系统各自训练模型、各自部署服务、各自走一套发布流程。这种分离式架构在早期确实很实用。普通话模型用通用ASR方案方言模型单独收集语料、单独调参两边的迭代节奏互不打扰。业务方要加一个新方言团队就再起一个独立服务数据管线、训练脚本、推理环境全部复制一份。听起来笨重但在方言种类只有两三种、并发量也不高的时候这套方案的交付速度反而是最快的。可问题恰恰出在“快”上。方言系统一旦从“能做演示”变成“要扛线上流量”分离式架构的账就算不过来了。我先说一个最直观的数字当时我们维护了6个方言识别服务每个服务背后是独立的GPU实例、独立的模型版本、独立的上线脚本。每次版本发版运维要把6套流程各跑一遍光回归测试就要花掉大半天。这还只是维护成本还没算上模型之间的资源隔离浪费。1.2 规模上来之后暴露的问题当方言数量从2个涨到8个业务从单场景扩展到多场景后分离式架构的瓶颈开始集中爆发。第一个问题是特征能力不共享。普通话模型的语料多、训练充分对声学特征的提取能力明显强于小方言模型。但在分离式架构下普通话模型学到的好特征完全没法迁移给方言模型。每个小方言都要从零开始学声学表征数据量又不够识别效果始终上不去。这是很典型的“富者愈富、贫者愈贫”问题。第二个问题是方言边界模糊导致路由失效。系统里最初有一个前置的语种识别模块先判断用户说的是什么方言再把音频路由给对应的方言模型。但实际场景中四川话和重庆话、东北话和北京话边界非常模糊甚至同一句话里夹杂着普通话和方言常用词。前置路由一旦判错后面整套识别链路就废了。我们当时还试过让用户手动选择方言结果发现大多数用户根本分不清自己说的是“西南官话”还是“中原官话”。第三个问题是资源利用率极低。6个独立服务每个服务都要预留峰值冗余但方言流量的波峰波谷并不同步。有的方言服务在白天排队晚上空转另一个服务正好反过来。整体算下来GPU利用率不到40%。做容量评估的时候运维同学非常头疼因为没法用一套资源池去平滑所有方言的流量。此外还有冷启动困境。每次要接入一种新方言从数据收集、清洗、标注到训练调优平均要8到10周。这对业务来说太慢了很多新方言的需求从提出到上线业务同学早就换了优先级。所以与其说“从分离到统一”是一个主动的技术选型不如说是被业务倒逼出来的架构演进。核心诉求就三条让方言模型之间共享声学能力、让线上服务按统一入口调度、让新方言的接入成本大幅下降。2. 演进目标与整体设计思路2.1 先搞清楚“统一”到底要统一什么规划架构演进前我带着团队把“统一”这个词拆了一遍。很多人一提统一架构第一反应是“所有方言共用一个模型”。但实际做下来这个想法既激进也不现实。不同方言在音素体系、声调系统、语法习惯上差异太大强拧成一个模型结果往往是谁都照顾不好。我们最终定的方向是“分层统一”不是“全栈统一”。具体来说数据层统一所有方言语料进入同一套数据规范和标注体系不再各搞各的格式。特征层统一共用一套声学特征提取和编码网络让普通话数据量训练出来的特征能力下沉共享。模型层统一采用“共享encoder 方言专家分支”的结构不同方言共享底层声学编码器在高层各自保留专家分支。服务层统一对外只暴露一个方言识别入口由网关做语言检测和模型调度不再让业务方对接多个方言服务。这套设计的好处是既保留了方言之间的差异性又最大程度地让共性部分被复用。用生活化的类比说以前每个方言项目从打地基开始盖楼互不往来现在是所有方言共用同一层地基和承重墙只是每栋楼的高层装修风格不同。地基的成本摊薄了楼也建得更快。2.2 架构演进的分阶段路线统一架构不是一蹴而就的我们分了三个阶段推进。第一阶段是“先统一数据”。把所有方言语料从各家自己的目录结构、标注格式中解放出来统一成标准化的数据集。这一步不涉及线上系统改造风险很低但却是后面所有工作的基础。当时我们把每个方言的音频统一成16kHz、16bit、单声道的WAV格式标注文本统一按“汉字 方言音标 普通话等效词”三层结构记录。数据不规范带来的坑后面会单独说这里先不展开。第二阶段是“统一训练框架”。把原来普通话模型和各个方言模型的独立训练脚本合并成一个多任务训练框架让所有方言模型在同一个训练任务中共同更新共享encoder。这一步完成后普通话模型的语料优势就开始通过共享encoder传导给方言模型方言识别率有了明显提升。第三阶段是“统一线上服务”。把多个独立推理服务合并为一个统一推理网关内部按请求动态路由到对应的模型分支。这一步改造最重涉及服务架构、部署方式、监控链路的大调整但也是收益最明显的一步。2.3 为什么不是直接重建一套有一个很现实的问题既然分离式架构这么不好为什么不推倒重来直接建一套全新的统一架构我的回答是工程演进最怕的不是慢而是推翻重建带来的业务不可控。原系统虽然问题多但已经在线上稳定跑了大半年积累了大量真实语料和线上日志。这些资产如果因为重建而废弃代价极高。我们采取的是“渐进式替换”策略。旧系统继续运行新架构在旁路建设通过离线对比测试不断验证效果。等统一模型在方言识别准确率、时延、资源占用这几个关键指标上全面超过旧系统了再切流量。切流量的方式也是灰度的先放5%的线上请求试运行观察一周再逐步放量。这样做还有个额外好处因为新旧架构并行所有历史版本都有据可查出问题随时可以回滚。对新架构的模型效果有疑问时直接用旧系统的线上日志做对比评测不用重新构造测试集。这种“两条腿走路”的方式虽然前期投入大但长期来看是风险最低的演进路径。3. 关键工程环节与实施细节3.1 数据层统一方言资源池的建设数据层统一是整个演进的基石也是最容易忽略的一环。很多团队做架构演进时把注意力放在模型结构上结果数据规范不统一后面所有环节都在为早期偷的懒买单。我们做的第一件事是建立统一的方言标注规范。以前各业务线对“方言标注”的理解不一样有的只标注汉字转写有的额外标了方言音标有的把语气词、重复词全部清理掉了。统一后我们明确每一条语料必须包含三个字段text_standard普通话等效转写用于跨方言的文本对齐。text_dialect方言原字转写保留方言特有的表达方式。phonetic_local方言音标序列用国际音标统一标写不搞各地方言自己发明的注音方案。这三个字段缺一不可。为什么要保留三份而不是只留一份因为模型训练时不同任务需要不同粒度的监督信号。普通话等效转写帮助模型理解语义方言原字转写帮助模型学习方言用词习惯方言音标帮助模型对齐声学特征。多任务训练中这三类标注各有各的用途。第二个关键动作是音频预处理标准化。我们在数据管线里增加了统一的预加重、分帧、加窗、端点检测步骤。之前各团队自己处理音频有的不去除首尾静音有的采样率不统一导致模型训练时总是学到奇怪的噪声特征。统一后所有音频在进入特征提取前先跑一遍相同的预处理流程保证声学特征的一致性。这里要特别提醒一个容易踩的坑方言语料里经常夹杂着背景噪声、电视声、他人说话声如果只是简单地把整段音频喂给模型模型会学到“方言 噪声”的耦合特征。我们后来在数据管线里专门加了一道自动清洗的流程用简单的能量检测把过长静音切掉同时用人工抽检的方式挑出重音、混响严重的数据。整个过程很费人力但方言识别系统对数据质量的敏感度比普通话系统高得多这步省不得。3.2 模型层统一共享参数与方言分支模型层的设计是整个演进的核心。我们最终采用了“共享encoder 方言专家分支”的架构这种结构在业界也叫“硬参数共享 专家混合”的思路。架构的关键参数可以概括为三层底层声学特征提取层CNN层全共享这部分负责把原始音频变成声学特征不同方言在声音底层有很多共通之处全共享可以最大化利用普通话大语料训练出来的特征提取能力。中间encoder层Transformer层共享大部分参数但按方言簇预留少量独立的adaptor模块这里说的“方言簇”是把语言特性相近的方言归为一组比如西南官话、中原官话、粤语、闽语各成一组。同一簇内共享adaptor减少参数总量。顶层输出层按方言独立每个方言分支有自己的CTC/Attention解码头和词表映射保证输出空间的独立性。训练时每个batch会同时混入多个方言的数据loss由各方言分支的loss加权求和。权重的设置很关键后面会详细讲。这个结构的好处是参数的共享程度可以按方言之间的相似度动态调整而不是一刀切。最初我们尝试过更激进的“全共享单模型”也就是所有方言共用一个模型、一个输出词表。结果发现广东话和东北话在词表、音素上差异太大模型训练时loss震荡非常厉害最终识别效果反而不如分离模型。后来改成“共享encoder 独立分支”后问题就得到了有效控制。这说明统一不是目的效果好才是目的。3.3 服务层统一单入口多方言路由模型层的统一做完后服务层还维持着“一个方言一个服务”的状态。这时候必须推进一步把多个服务合并成统一的服务入口。服务层统一的关键设计是引入一个轻量级的“方言感知路由层”。这个路由层承担两个任务一是检测输入音频属于哪种方言簇二是把请求调度到对应的模型分支。同时路由层还维护了一个动态语言包机制——识别结果中的方言词通过语言包映射到普通话等效词方便下游业务系统统一消费。方言检测我们一开始用的是独立的小模型后来发现直接用统一模型在encoder之后出来的特征向量做分类准确率更高还省掉了额外部署。原因也很简单统一模型自己就见过所有方言的数据它产生的特征比单独训练的小模型更有区分度。服务层统一还有一个实际收益资源池可以共用。以前6个服务各自留峰值冗余现在统一服务按总量预留资源流量空闲时可以把GPU分给离线训练任务资源利用率从40%提升到了70%以上。不过服务层统一要特别注意兼容性问题。旧接口的请求参数、返回格式要尽量保持不变否则业务方就得跟着改代码。我们当时做了一个适配层把统一服务内部的响应格式重新映射回旧的Api结构这样业务方的老代码一行都不用改。这种细节虽然不性感但决定了你能不能平滑切换。4. 训练与调优的关键参数实践4.1 语料配比与采样策略统一模型的训练最大的坑在于语料配比。如果把所有方言数据混在一起随机采样普通话数据量通常占绝对多数模型会严重偏向普通话方言识别效果惨不忍睹。如果强行让每种方言等量采样又会因为某些小方言的语料太少模型在抽样时反复见到同一条数据导致过拟合。我们最终的方案是“按簇配比 动态采样权重”。先把方言按语言特征分成几个簇簇内语料合并再根据业务流量占比和目标方言效果为每个簇设置采样权重。具体权重不是拍脑袋定的而是通过一组消融实验试出来的。我们试过4:3:2:1、5:2:2:1等各种配比最终选了接近业务流量分布、同时给小方言簇保底权重的方案。动态采样权重的意思是训练初期让数据量大的方言簇多露脸帮助模型快速收敛公共的声学特征训练后期提高小方言簇的权重让模型把注意力放到拟合方言差异细节上。这种做法有点像先学共性的“普通话”再学个性的“方言”训练收敛速度更快最终效果也更均衡。这里补充一个实操细节训练过程中要定期用一套固定的评测集来验证各方言的效果不要只盯着单一指标。我们早期只看综合CER字符错误率结果某次调参后综合CER很好看但细看粤语CER比之前差了3个百分点。后来改成每次实验必须输出按方言维度拆分的CER矩阵哪个方言退步了一清二楚。4.2 模型结构与loss设计模型结构上我们以Conformer作为共享encoder的主干。Conformer结合了CNN的局部特征提取能力和Transformer的全局上下文建模能力在语音识别里是经过验证的主流选择。encoder层数设为12层hidden size为256参数量控制在可控范围。方言专家分支这边每个分支是一个2层的轻量decoder配合独立的音素分类头和字词映射模块。分支之间不共享参数保证可以独立输出方言特色的音素序列。loss设计上用了一个加权多任务目标主任务Loss是CTC Loss和Attention Loss的加权和这两个是端到端语音识别的经典组合。辅助任务是一个方言簇分类Loss鼓励encoder输出的特征携带方言类别信息帮助统一模型更好地感知“现在说的到底是哪里的方言”。这个辅助loss的权重不能太高否则模型会把太多精力放在区分方言上反而干扰识别我们设置为0.1到0.2之间的一个值具体的值需要根据训练曲线的表现来微调。4.3 推理阶段的性能优化模型训练完成是个开始推理优化才是真正上线的硬仗。统一模型比原来的单方言模型大了不少如果直接部署推理时延会超标。我们做了三件事把时延压了下来。第一是INT8量化。把共享encoder的权重从FP16量化到INT8精度损失很小实测各方言CER平均上升不到0.2个百分点但推理速度提升了约60%。第二是batch请求合并。方言识别服务的流量有一个特点单位时间内请求很多但每个请求音频都很短。我们把高并发下同一毫秒窗口内的请求合并成一个batch一次性喂给模型推理。这个操作需要和上层网关联动网关攒一批请求再转发给推理服务。实测batch size从1增加到8GPU吞吐提升了近3倍。第三是动态分支加载。某个方言分支如果长时间没有请求就把它从显存中卸载等路由层检测到有该方言请求时再动态加载。这个机制对冷门方言特别有用大大降低了显存占用。推理优化做完后的最终指标说出来给大家一个参考统一模型在GPU上的平均端到端识别时延在300毫秒以内比原来的分离式架构平均450毫秒还快了不少。因为原来的架构里有一步前置方言路由需要串行调用两个模型统一架构把这个环节省掉了。5. 落地过程中的常见问题与排查5.1 方言混淆怎么压统一模型上线后最让我们头疼的问题是“方言混淆”。具体表现是用户说的是四川话模型识别成了带一点西南官话口音的普通话或者把湖南话和四川话的特征混淆在一起输出的文字四不像。根因有两方面。一是方言之间的相似性西南官话和中原官话的发音、用词本来就有大量重叠模型在有限的上下文里很难分辨。二是训练数据中相近方言的边界不清晰标注的时候同一个词在四川话和贵州话里的音标记法可能不一样模型就学乱了。我们采取的应对措施是“在数据层面强化边界”。一方面把相近方言的数据单独拉出来做对比训练让模型看到“同一个词在不同方言里的发音差异”。另一方面在文本后处理里增加方言词约束表模型如果输出了多个候选词后处理阶段会根据上下文和方言偏好选择最合适的那个词。5.2 新方言接入的成本怎么控制架构演进之前接入一个新方言的周期是8到10周。演进后这个周期压缩到了3到4周但并没有变成“零成本接入”。很多人都误以为统一架构做完了加个新方言只是加个数据集的活儿实际上没有那么简单。新方言接入流程中真正的耗时点在数据准备上而不是模型上。收集新方言的音频、清洗噪声、人工转写、按统一规范标注这些环节加起来至少2周。模型侧的工作量反而不大数据准备好后只需在新方言分支上做有限步数的微调即可因为共享encoder已经把通用的声学特征学好了。这里有个经验分享新方言接入时尽量找和已有方言在语言特征上相似的“兄弟方言”的数据做预训练初始化。比如接入贵州话时可以用现有的四川话分支作为起点微调收敛速度明显快于从零初始化。这是因为两种方言同属西南官话体系音系和词汇体系高度相似迁移起来非常自然。5.3 线上兼容和回滚问题架构演进过程中线上兼容性是运维事故的高发区。我们有几次发版就是因为新旧接口字段格式不一致导致下游业务拿到空的识别结果。排查这类问题我们建立了一套“线上兼容测试清单”每次发版前必须逐项确认请求参数中的采样率字段是否兼容旧格式返回结果中的文本编码方式是否一致特别注意不要从UTF-8悄悄变成UTF-16新增的方言标记字段是否对旧客户端透明超时和重试逻辑是否需要调整因为统一服务在冷启动时会比平时慢。再有就是回滚预案。统一架构在技术上确实更合理但一旦出问题回滚的复杂度比分离式架构高因为一个服务挂了影响的是所有方言不像以前挂一个方言服务只影响单一方言。所以每次发版前我们都要求新模型和旧模型同时保留在线上环境通过流量灰度控制随时切回。灰度切流的节奏我们一般的做法是先切1%流量观察2小时错误率和时延如果没有异常提升到10%再观察一天之后是50%、100%。每次提升前都要跑一轮方言维度的CER对比确保没有方言识别效果明显下滑。6. 演进的经验与后续打算这套架构演进从启动到基本完成前后花了大约5个月。回过头来看我最深的体会是架构演进本质上是一场“取舍”的艺术不是技术越新越好也不是架构越统一越好而是要在业务需求、资源成本、团队能力之间找到平衡点。有几个经验想分享给做同类系统的朋友第一数据规范一定要先行。很多团队做架构演进一上来就扎进模型结构里结果数据规范不到位模型怎么调都出不了效果。先把标注规范、音频格式、数据管线全部统一后面所有迭代都会顺很多。第二不要追求一步到位“全统一”。方言之间的差异性决定了完全统一的单模型在现阶段很难做到效果最优。共享底层、保留分支的结构兼顾了共性和个性是一个比较务实的中间态。第三量化评测比感觉靠谱。每次模型迭代都按方言维度拆开看指标不要只看综合指标。这样能快速定位哪些方言被“牺牲”了方便及时调整权重和结构。目前这套统一架构已经支撑了公司内部的方言语音搜索、方言客服、方言内容转写三个业务场景。后续我打算做两个方向一是尝试把方言分支从“按语系聚类”细化为“按具体方言独立”看看参数增加得可控不可控二是把方言识别和方言合成放在同一个underlying模型里做多任务联合训练让识别和合成的能力互相增强。这条路还在探索中等有阶段性成果了再出来分享。最后再分享一个实用的小技巧如果你也在做类似的系统演进建议从第一天就建立一套“方言能力看板”把每种方言的准确率、时延、调用量、资源占用全部可视化。演进过程中你一定会遇到各种“感觉变好了”或者“感觉变差了”的声音这时候拉出看板数据用数字说话能避免很多无休止的争论。
返回列表