PaddleSpeech 定制化流式语音识别实战:基于 Slot WFST 解码图实现打车报销场景的稀有地名精准识别
【免费下载链接】PaddleSpeechEasy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.项目地址: https://gitcode.com/paddlepaddle/PaddleSpeech
本篇技术指南围绕 PaddleSpeech 仓库中 demos/custom_streaming_asr 演示目录展开,讲解如何通过「带槽位(Slot)的 WFST 解码图定制」解决通用 ASR 在稀有词(如打车场景中的生僻地名)上识别准确率不足的问题,并给出从 Docker 环境搭建、WebSocket 服务端启动、客户端调用到定制解码图构建的完整可运行流程。读完本文,你将掌握 PaddleSpeech 定制化流式识别(Customized Streaming ASR)的整体架构、核心参数含义,以及如何在本地复现一个能识别「打车到某地名」句式的端到端演示。
一、为什么需要定制化 ASR:稀有词识别的痛点
通用语音识别系统在常见语料上表现良好,但在某些垂直场景中,需要对特定稀有词保持极高的识别准确率。典型例子是导航软件中的地名识别:一个城市的街道名、小区名、商圈名往往不在通用词典里,通用模型很容易将其识别错误。
本文演示的 demo 正是取自打车报销单场景:用户说「打车到某某地名,多少钱」,系统需要准确识别出其中的稀有地址。原文档给出了两种解法:
- 在线识别演示(即本目录 demos/custom_streaming_asr 的用法):通过 WebSocket 服务端/客户端完成定制化流式识别;
- 定制解码图构建:仓库中与之配套的完整建图脚本位于 runtime/examples/custom_asr,展示如何从零构建自己的解码图(TLG.fst)。
两者的核心思想一致:在解码图的语言模型层(G)中为稀有词预留"槽位"(Slot),再通过 WFST 的 replace 操作把槽位替换成可扩展的定制词表,从而让解码器"认识"这些稀有词。
二、核心原理:Slot WFST 定制解码图
定制化的关键在于修改解码图,而非重新训练声学模型。原文档用三张图说明了这一过程,其核心链路如下:
- G with slot:在语言模型 G 中,把需要定制的部分抽象为一个槽位。例如句子模板「打车到 "address_slot"」,其中
address_slot就是一个占位符槽位,表示「此处可填入任意地名」。 - address slot 的 WFST:单独构建一个 address 槽位对应的 WFST,里面列出所有需要识别的地名。想新增地名时,只需扩充这个槽位图即可,无需重新训练声学模型。
- fstreplace 替换:执行
G = fstreplace(G_with_slot, address_slot),将主图(G_with_slot)中的槽位用槽位图(address_slot)替换,最终得到定制化的解码图。替换后得到的定制化 TLG.fst 即可直接用于解码。
从 runtime/examples/custom_asr/run.sh 的源码可以看到,该演示实际预留了 5 个槽位,分别对应打车报销单中常见的实体类型:
echo "<MONEY_SLOT> 一" >> $dir/local/dict/lexicon.txt echo "<DATE_SLOT> 一" >> $dir/local/dict/lexicon.txt echo "<ADDRESS_SLOT> 一" >> $dir/local/dict/lexicon.txt echo "<YEAR_SLOT> 一" >> $dir/local/dict/lexicon.txt echo "<TIME_SLOT> 一" >> $dir/local/dict/lexicon.txt这些槽位会被加入 lexicon,以避免语言模型训练脚本将其过滤掉(源码注释:add slot to lexicon, just in case the lm training script filter the slot)。整个定制流程与最终识别效果(WER 1.23%)记录在 runtime/examples/custom_asr/README.md 中。
三、整体架构:WebSocket 服务端 + 客户端
本 demo 采用 WebSocket 进行流式语音识别交互,架构上分为两部分:
- 服务端:
websocket_server_main负责加载声学模型(Paddle 推理模型)、解码图(TLG.fst)、词表与 cmvn 统计量,并监听指定端口接收客户端流式音频。 - 客户端:
websocket_client_main按wav.scp逐条读取语音,以流式 chunk 方式发送给服务端并获取识别结果。
服务端入口在 runtime/engine/asr/server/websocket/websocket_server_main.cc,其核心逻辑非常清晰:通过 gflags 解析命令行参数,组装RecognizerResource(包含声学模型、特征管线、TLG 解码器配置),随后启动WebSocketServer并开始监听:
DEFINE_int32(port, 8082, "websocket listening port"); ppspeech::RecognizerResource InitRecognizerResoure() { ppspeech::RecognizerResource resource; resource.acoustic_scale = FLAGS_acoustic_scale; resource.feature_pipeline_opts = ppspeech::FeaturePipelineOptions::InitFromFlags(); resource.model_opts = ppspeech::ModelOptions::InitFromFlags(); resource.tlg_opts = ppspeech::TLGDecoderOptions::InitFromFlags(); return resource; } int main(int argc, char *argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, false); google::InitGoogleLogging(argv[0]); ppspeech::RecognizerResource resource = InitRecognizerResoure(); ppspeech::WebSocketServer server(FLAGS_port, resource); LOG(INFO) << "Listening at port " << FLAGS_port; server.Start(); return 0; }所有可配置参数(声学特征、解码、模型相关)统一定义在 runtime/engine/asr/decoder/param.h,demo 文档中提到的port、graph_path、model_path均来自这里。
四、环境准备:Paddle 2.2.2 Docker 镜像
原文档要求使用 Paddle 2.2.2 的 Docker 镜像搭建运行环境。CPU 版拉取与启动命令如下:
sudo docker pull registry.baidubce.com/paddlepaddle/paddle:2.2.2 sudo docker run --privileged --net=host --ipc=host -it --rm \ -v $PWD:/paddle --name=paddle_demo_docker \ registry.baidubce.com/paddlepaddle/paddle:2.2.2 /bin/bash其中关键参数说明:
--privileged:授予容器特权,保证网络与设备访问正常;--net=host:容器与宿主机共享网络栈,便于客户端直连服务端口;--ipc=host:共享进程间通信命名空间,配合 WebSocket 流式传输使用;-v $PWD:/paddle:将当前目录(即 demo 目录)挂载到容器内/paddle;--rm:退出容器后自动清理。
如果使用 GPU 环境,仓库还提供了 setup_docker.sh,其内部使用sudo nvidia-docker run启动同一镜像,以便声学模型在 GPU 上推理。
五、启动服务端:websocket_server.sh 详解
进入容器后执行:
cd /paddle bash websocket_server.sh该脚本(demos/custom_streaming_asr/websocket_server.sh)实际完成两步工作。
5.1 第一步:下载并解压资源
脚本首先检查 cmvn 文件是否存在,不存在则下载并解压resource.tar.gz:
if [ ! -f $cmvn ]; then wget -c https://paddlespeech.cdn.bcebos.com/s2t/paddle_asr_online/resource.tar.gz tar xzfv resource.tar.gz ln -s ./resource/data . fi解压后在resource目录下会得到以下内容(与文档描述一致):
| 目录 | 作用 |
|---|---|
model | 声学模型(Deepspeech2 在线模型,jit 推理格式) |
graph | 解码图(TLG.fst)与词表(words.txt) |
lib | 运行所需动态库 |
bin | 可执行程序(websocket_server_main 等) |
data | 测试语音与 wav.scp |
同时,脚本会ln -s ./resource/data .创建data软链接,供 cmvn 与语音数据引用。
5.2 第二步:启动 WebSocket 服务
资源就绪后,脚本调用websocket_server_main并传入一组参数:
websocket_server_main \ --cmvn_file=$cmvn \ --streaming_chunk=0.1 \ --use_fbank=true \ --model_path=$model_dir/avg_10.jit.pdmodel \ --param_path=$model_dir/avg_10.jit.pdiparams \ --model_cache_shapes="5-1-2048,5-1-2048" \ --model_output_names=softmax_0.tmp_0,tmp_5,concat_0.tmp_0,concat_1.tmp_0 \ --word_symbol_table=$graph_dir/words.txt \ --graph_path=$graph_dir/TLG.fst --max_active=7500 \ --port=8881 \ --acoustic_scale=12各参数含义如下:
| 参数 | 示例值 | 说明 |
|---|---|---|
cmvn_file | ./data/cmvn.ark | 特征均值方差归一化统计量文件 |
streaming_chunk | 0.1 | 流式解码的音频分块时长(秒),越小实时性越好 |
use_fbank | true | 使用 Fbank 特征(false 则为线性特征) |
model_path/param_path | avg_10.jit.pdmodel/avg_10.jit.pdiparams | Paddle 推理模型结构文件与参数文件 |
model_cache_shapes | 5-1-2048,5-1-2048 | 流式模型各层缓存张量的 shape,用于跨 chunk 传递状态 |
model_output_names | softmax_0.tmp_0,... | 模型输出节点名称,对应 CTC softmax 与缓存输出 |
word_symbol_table | words.txt | 词符号表,将解码输出映射为文字 |
graph_path | TLG.fst | 解码图路径,定制化 ASR 的核心输入 |
max_active | 7500 | 解码时保持活跃的 token 数量上限 |
port | 8881 | 服务监听端口 |
acoustic_scale | 12 | 声学分数缩放系数 |
5.3 参数定义出处
以上参数的默认值与完整定义可追溯到 runtime/engine/asr/decoder/param.h,例如:
DEFINE_string(cmvn_file, "", "read cmvn"); DEFINE_string(model_path, "avg_1.jit.pdmodel", "paddle nnet model"); DEFINE_double(acoustic_scale, 1.0, "acoustic scale"); DEFINE_string(graph_path, "", "decoder graph"); DEFINE_string(word_symbol_table, "", "word symbol table"); DEFINE_int32(max_active, 7500, "max active"); DEFINE_double(beam, 15.0, "decoder beam"); DEFINE_double(lattice_beam, 7.5, "decoder beam"); DEFINE_double(blank_threshold, 0.98, "blank skip threshold"); DEFINE_int32(nbest, 10, "nbest for ctc wfst or prefix search");其中beam、lattice_beam、blank_threshold、nbest等参数在 demo 脚本中未显式给出,均使用param.h中的默认值;若需调整解码精度与速度的平衡,可直接在启动命令中追加同名参数。另外port的默认值(8082)定义在 websocket_server_main.cc 中,demo 通过显式传入--port=8881覆盖。
六、启动客户端:websocket_client.sh
服务端运行后,在另一个终端执行:
bash websocket_client.sh客户端脚本(demos/custom_streaming_asr/websocket_client.sh)内容如下:
data=$PWD/data wav_scp=wav.scp export GLOG_logtostderr=1 websocket_client_main \ --wav_rspecifier=scp:$data/$wav_scp \ --streaming_chunk=0.36 \ --port=8881参数说明:
--wav_rspecifier=scp:$data/$wav.scp:以 Kaldi scp 格式指定待识别的语音集合(wav.scp位于解压出的data目录下);--streaming_chunk=0.36:客户端每次发送的音频分块时长(秒),与服务端0.1的 chunk 配合完成流式解码;--port=8881:服务端监听端口,必须与websocket_server.sh中保持一致。
两个脚本顶部均执行. path.sh(demos/custom_streaming_asr/path.sh),它把resource/lib加入LD_LIBRARY_PATH、把resource/bin加入PATH,保证websocket_server_main/websocket_client_main可被直接调用。
七、运行结果解读
客户端日志中可以看到类似如下的输出:
0513 10:58:13.827821 41768 recognizer_test_main.cc:56] wav len (sample): 70208 I0513 10:58:13.884493 41768 feature_cache.h:52] set finished I0513 10:58:24.247171 41768 paddle_nnet.h:76] Tensor neml: 10240 I0513 10:58:24.247249 41768 paddle_nnet.h:76] Tensor neml: 10240 LOG ([5.5.544~2-f21d7]:main():decoder/recognizer_test_main.cc:90) the result of case_10 is 五月十二日二十二点三十六分加班打车回家四十一元逐行含义:
wav len (sample): 70208:当前语音共 70208 个采样点(约 4.4 秒 @16kHz);feature_cache.h:52] set finished:特征缓存写入完成,进入流式解码状态;paddle_nnet.h:76] Tensor neml: 10240:Paddle 神经网络前向计算输出的帧数信息;- 最后一行
LOG (...:90) the result of case_10 is ...即为最终识别结果:五月十二日二十二点三十六分加班打车回家四十一元。
注意该结果中包含了日期(五月十二日)、时间(二十二点三十六分)、金额(四十一元)等槽位化实体,正是定制解码图生效的体现——通用模型很难在单句中同时精确给出这些稀有组合。类似的 batch 识别与 WER 统计流程也可参考 runtime/examples/custom_asr/run.sh 的 stage 4:使用recognizer_test_main对wav.scp批量解码,再用utils/compute-wer.py与标注data/wav.trans比对计算字错误率。
八、从零构建自己的定制解码图
如果不想使用预置的resource.tar.gz,仓库在 runtime/examples/custom_asr 提供了完整的建图教程脚本,对应原文档中提到的speechx/examples/custom_asr流程。执行bash run.sh后按 stage 依次完成:
- stage 1 制作词典:由声学模型词表
vocab.txt与发音词典data/lexicon.txt通过 utils/fst/prepare_dict.py 生成 lexicon,并手工追加<MONEY_SLOT>、<DATE_SLOT>、<ADDRESS_SLOT>、<YEAR_SLOT>、<TIME_SLOT>五个槽位条目; - stage 2 训练带槽语言模型:调用 local/train_lm_with_slot.sh 生成包含槽位 token 的 n-gram 语言模型;
- stage 3 构建 TLG:依次执行 local/compile_lexicon_token_fst.sh(生成 T、L 图)、local/mk_slot_graph.sh(生成槽位图)、local/mk_tlg_with_slot.sh(执行 fstreplace 并组合成 TLG.fst);
- stage 4 测试:用
recognizer_test_main解码data/wav.scp,通过 utils/compute-wer.py 计算 WER,仓库记录的参考结果约为Overall 1.23%(N=1134, C=1126, S=6, D=2, I=6)。
对开发者而言,定制自己的地名/人名/术语只需在槽位 WFST 中扩充对应词条并重新执行 stage 3,而无需重新训练声学模型,这正是该方案在垂直场景下快速落地的价值所在。
九、小结
PaddleSpeech 的定制化流式 ASR 演示给出了一条「不改声学模型、只改解码图」的稀有词识别路径:通过带槽位的 G 图与fstreplace组合出定制化 TLG.fst,配合 WebSocket 流式服务完成在线识别。本文覆盖了从 Docker 环境搭建、服务端/客户端脚本逐参数解读、日志结果分析到定制解码图构建全流程,并给出了各参数在 param.h 中的定义出处,可直接作为在 demos/custom_streaming_asr 目录下复现实验、或参考 runtime/examples/custom_asr 构建自有定制词表的实战手册。
【免费下载链接】PaddleSpeechEasy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.项目地址: https://gitcode.com/paddlepaddle/PaddleSpeech
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考