文章来源于新一代Kaldi,作者NGK编辑部
本文介绍据我们所知, 第一个使用ncnn进行语音识别的开源项目sherpa-ncnn。
简介
sherpa[1]目前支持使用PyTorch做推理框架,进行语音识别。当模型使用PyTorch训练好之后,我们可以把模型导出成torchscript支持的格式,脱离Python, 使用C++在sherpa中进行部署。
PyTorch对CPU和GPU都有良好的支持,适合在基于x86架构的服务器上使用。可是,PyTorch是一个重量级的框架,对资源的使用要求较高,对嵌入式的支持也不是那么的友好。某些情况下,我们也希望构建一个没有依赖或者引入很轻量级依赖的语音识别应用。这时,我们就需要寻找PyTorch以外的推理框架。
由于模型是使用PyTorch训练的,如果我们使用其他的框架,首先要解决的问题,就是模型格式的转换。PyTorch对onnx[2]提供了内置的支持,因此支持onnx格式的框架是我们的首选。
但是,并不是所有的PyTorch算子,都支持使用onnx进行导出。因此在选用 推理框架的时候,我们还要考虑该框架是否容易扩展。经过综合考虑,我们决定先支持ncnn[3]推理框架。一方面,它提供了PNNX[4]模型转换工具, 可以很方便地把PyTorch模型转为ncnn支持的格式;ncnn和PNNX的代码可读性和可扩展性都很不错,当碰到不支持的算子 时,我们可以很方便的扩展ncnn和PNNX。另一方面,尽管ncnn开源 已经有长达 5 年的时间,它的开发者社区仍然很活跃,并且 up 主还在不断的更新和维护ncnn; 当我们碰到问题的时候,可以很容易的获得帮助。
本文介绍如何使用sherpa-ncnn[5]进行语音识别。我们目前支持和测试过的平台有Linux,macOS,Windows,和Raspberry Pi等。
模型转换
我们以LSTM transducer[6]为例,描述如何把一个icefall[7]中训练好的模型从PyTorch转换为ncnn支持的格式。
由于我们使用了带projection的LSTM模型,目前PyTorch还不支持通过onnx的方式导出这类模型。这意味着直接通过先转成onnx格式, 再把onnx格式的模型转成ncnn支持的格式这条路,就行不通了。
剩下的选择就是通过PNNX进行模型转换。不幸的是ncnn和PNNX直到今天 (2022.10.14)才支持带projection的LSTM。幸运的是,ncnn和PNNX扩展性能很好,添加自定义算子的步骤简便、快捷, 为此我们修改了ncnn和PNNX,增加了对带projection的LSTM的支持。
在我们的要求下,
ncnn的开发者增加了上述功能。后续我们会使用ncnn和PNNX内置的实现。
解决了不支持的算子问题后,接下来就是正式的转模型了。目前PNNX只支持torch.jit.trace()导出的模型。因此,我们首先需要把模型通过torch.jit.trace()进行导出。所需命令如下:

注:
egs/librispeech/ASR是icefall[8]中的路径。上述命令假设你已经使用PyTorch完成了模型训练。
上述命令会生成以下 3 个文件:
./lstm_transducer_stateless2/exp/encoder_jit_trace-pnnx.pt./lstm_transducer_stateless2/exp/decoder_jit_trace-pnnx.pt./lstm_transducer_stateless2/exp/joiner_jit_trace-pnnx.pt
剩下的就是使用PNNX对torch.jit.trace()导出的文件进行转换。
下述命令展示了如何安装我们修改过后的ncnn和PNNX。

注:
export PATH=$PWD/src:$PATH把可执行程序pnnx所在的路径加入 到环境变量PATH。
安装好ncnn和PNNX之后,我们就可以把torch.jit.trace()导出的模型通过下面的命令转换成ncnn支持的格式:

它会生成下面几个文件:
./lstm_transducer_stateless2/exp/encoder_jit_trace-pnnx.ncnn.param./lstm_transducer_stateless2/exp/encoder_jit_trace-pnnx.ncnn.bin./lstm_transducer_stateless2/exp/decoder_jit_trace-pnnx.ncnn.param./lstm_transducer_stateless2/exp/decoder_jit_trace-pnnx.ncnn.bin./lstm_transducer_stateless2/exp/joiner_jit_trace-pnnx.ncnn.param./lstm_transducer_stateless2/exp/joiner_jit_trace-pnnx.ncnn.bin
至此,我们完成了模型从PyTorch到ncnn的转换。
验证
模型转换完之后,一个重要的步骤就是验证模型的正确性。一种方法是给定同样的输入, 逐层比对ncnn的输出是否和PyTorch的输出一致。这种方法耗时耗力, 一般只有在排查问题时,没有办法中的办法。
我们采用另外一种端到端的验证方法:看使用转换后的模型能否正确识别给定的音频。幸运的是ncnn也提供了Python的接口,我们可以直接在Python中验证模型的正确性。
我们提供两种模式的验证:
(1)离线识别验证
(2)流式识别验证
注:我们建议,在进行模型转换的时候,采用逐层转换的方法。转换一层,验证一层,并做好单元测试。
离线识别验证
命令如下:
cd egs/librispeech/ASR
./lstm_transducer_stateless2/ncnn-decode.py \
--bpe-model-filename ./data/lang_bpe_500/bpe.model \
--encoder-param-filename ./lstm_transducer_stateless2/exp/encoder_jit_trace-pnnx.ncnn.param \
--encoder-bin-filename ./lstm_transducer_stateless2/exp/encoder_jit_trace-pnnx.ncnn.bin \
--decoder-param-filename ./lstm_transducer_stateless2/exp/decoder_jit_trace-pnnx.ncnn.param \
--decoder-bin-filename ./lstm_transducer_stateless2/exp/decoder_jit_trace-pnnx.ncnn.bin \
--joiner-param-filename ./lstm_transducer_stateless2/exp/joiner_jit_trace-pnnx.ncnn.param \
--joiner-bin-filename ./lstm_transducer_stateless2/exp/joiner_jit_trace-pnnx.ncnn.bin \
/path/to/foo.wav有关模型转换和验证的详细文档,可以参考如下链接:
https://k2-fsa.github.io/icefall/model-export/export-ncnn.html
流式识别验证
命令如下:
./lstm_transducer_stateless2/streaming-ncnn-decode.py \
--bpe-model-filename ./data/lang_bpe_500/bpe.model \
--encoder-param-filename ./lstm_transducer_stateless2/exp/encoder_jit_trace-pnnx.ncnn.param \
--encoder-bin-filename ./lstm_transducer_stateless2/exp/encoder_jit_trace-pnnx.ncnn.bin \
--decoder-param-filename ./lstm_transducer_stateless2/exp/decoder_jit_trace-pnnx.ncnn.param \
--decoder-bin-filename ./lstm_transducer_stateless2/exp/decoder_jit_trace-pnnx.ncnn.bin \
--joiner-param-filename ./lstm_transducer_stateless2/exp/joiner_jit_trace-pnnx.ncnn.param \
--joiner-bin-filename ./lstm_transducer_stateless2/exp/joiner_jit_trace-pnnx.ncnn.bin \
/path/to/foo.wav使用
当验证转换后的模型没有问题时,我们就可以利用它进行语音识别了。这一步主要的工作就是使用ncnn提供的C++ API进行神经网络的计算, 然后实现解码算法,进行解码。
所有代码都开源在如下 repo:
https://github.com/k2-fsa/sherpa-ncnn
下面我们讲述如何编译sherpa-ncnn并用它进行语音识别 (包含非流式和流式)。
Linux/macOS 平台编译方法

上述命令会生成两个可执行程序:

sherpa-ncnn可以用于识别给定的音频文件 (非流式)sherpa-ncnn-microphone用于实时语音识别
值得注意的是,这两个可执行程序,只依赖系统库,没有任何的外部依赖。验证方法如下:

Windows 平台编译方法

上述命令会生成两个可执行程序:
./build/bin/Release/sherpa-ncnn.exe./build/bin/Release/sherpa-ncnn-microphone.exe
我们默认采用的是静态链接,这意味着上述两个.exe没有外部依赖。在一台Windows电脑上编译完后,可以直接拷贝到其他的Windows电脑运行。
arm 平台编译
sherpa-ncnn还支持交叉编译。首先配置工具链:
mkdir/ceph-fj/fangjun/software
cd/ceph-fj/fangjun/software
tar xvf /path/to/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.xz
export PATH=/ceph-fj/fangjun/software/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin:$PATH注:你可以选择适合你自己的工具链。
gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.xz可以从
https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads/8-3-2019-03
进行下载。
为了便于大家下载,我们在
huggingface上做了一个镜像。链接如下https://huggingface.co/csukuangfj/arm-linux-gcc
安装完工具链后,可以采用如下命令编译sherpa-ncnn:

上述命令生成如下两个文件
$ ls-lh build-arm-linux-gnueabihf/bin/
total 2.6M
-rwxr-xr-x 1 kuangfangjun root 1.3M Oct 14 23:00 sherpa-ncnn
-rwxr-xr-x 1 kuangfangjun root 1.4M Oct 14 23:00 sherpa-ncnn-microphone
$ ls-lh build-arm-linux-gnueabihf/bin/
total 2.6M
-rwxr-xr-x 1 kuangfangjun root 1.3M Oct 14 23:00 sherpa-ncnn
-rwxr-xr-x 1 kuangfangjun root 1.4M Oct 14 23:00 sherpa-ncnn-microphone
$ ls-lh build-arm-linux-gnueabihf/bin/
total 2.6M
-rwxr-xr-x 1 kuangfangjun root 1.3M Oct 14 23:00 sherpa-ncnn
-rwxr-xr-x 1 kuangfangjun root 1.4M Oct 14 23:00 sherpa-ncnn-microphone
$ ls-lh build-arm-linux-gnueabihf/bin/total 2.6M-rwxr-xr-x 1 kuangfangjun root 1.3M Oct 14 23:00 sherpa-ncnn-rwxr-xr-x 1 kuangfangjun root 1.4M Oct 14 23:00 sherpa-ncnn-microphone
$ file build-arm-linux-gnueabihf/bin/sherpa-ncnnbuild-arm-linux-gnueabihf/bin/sherpa-ncnn: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, with debug_info, not stripped
nbsp;file build-arm-linux-gnueabihf/bin/sherpa-ncnn
build-arm-linux-gnueabihf/bin/sherpa-ncnn: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, with debug_info, not stripped
nbsp;file build-arm-linux-gnueabihf/bin/sherpa-ncnn
build-arm-linux-gnueabihf/bin/sherpa-ncnn: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, with debug_info, not stripped
aarch64 平台编译
首先配置工具链:
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
tar xvf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz -C /ceph-fj/fangjun/software
exportPATH=/ceph-fj/fangjun/software/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH
注:你可以选择适合你自己的工具链。
安装完工具链后,可以采用如下命令编译sherpa-ncnn:

上述命令生成如下两个文件
$ls-lh build-aarch64-linux-gnu/bin/
total 4.4M
-rwxr-xr-x 1 kuangfangjun root 2.2M Oct 14 23:27 sherpa-ncnn
-rwxr-xr-x 1 kuangfangjun root 2.2M Oct 14 23:27 sherpa-ncnn-microphone
$ls-lh build-aarch64-linux-gnu/bin/
total 4.4M
-rwxr-xr-x 1 kuangfangjun root 2.2M Oct 14 23:27 sherpa-ncnn
-rwxr-xr-x 1 kuangfangjun root 2.2M Oct 14 23:27 sherpa-ncnn-microphone
$ls-lh build-aarch64-linux-gnu/bin/
total 4.4M
-rwxr-xr-x 1 kuangfangjun root 2.2M Oct 14 23:27 sherpa-ncnn
-rwxr-xr-x 1 kuangfangjun root 2.2M Oct 14 23:27 sherpa-ncnn-microphone
$ls-lh build-aarch64-linux-gnu/bin/total 4.4M-rwxr-xr-x 1 kuangfangjun root 2.2M Oct 14 23:27 sherpa-ncnn-rwxr-xr-x 1 kuangfangjun root 2.2M Oct 14 23:27 sherpa-ncnn-microphone
$ file build-aarch64-linux-gnu/bin/sherpa-ncnnbuild-aarch64-linux-gnu/bin/sherpa-ncnn: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1,forGNU/Linux 3.7.0, BuildID[sha1]=30d340e9aff6feb75605548b4abb1ca89f9de093, with debug_info, not stripped
nbsp;file build-aarch64-linux-gnu/bin/sherpa-ncnn
build-aarch64-linux-gnu/bin/sherpa-ncnn: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1,forGNU/Linux 3.7.0, BuildID[sha1]=30d340e9aff6feb75605548b4abb1ca89f9de093, with debug_info, not stripped
nbsp;file build-aarch64-linux-gnu/bin/sherpa-ncnn
build-aarch64-linux-gnu/bin/sherpa-ncnn: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1,forGNU/Linux 3.7.0, BuildID[sha1]=30d340e9aff6feb75605548b4abb1ca89f9de093, with debug_info, not stripped
识别
编译好sherpa-ncnn之后,我们就可以用转换过的模型进行语音识别了。
注:目前sherpa-ncnn只实现了greedy search解码方法,不带任何形式的外部语言模型。
为了方便大家测试,我们针对英语和中文,分别提供了转换后的模型。模型下载方法如下:
下载英文模型
git lfs install
gitclonehttps://huggingface.co/csukuangfj/sherpa-ncnn-2022-09-05
下载完后,我们可以得到下述文件:

文件大小信息如下:
$ls-lh tokens.txt
-rw-r--r-- 1 kuangfangjun root 5.0K Sep 7 15:56 tokens.txt
$ls-lh bar/
total 161M
-rw-r--r-- 1 kuangfangjun root 503K Sep 5 15:21 decoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.bin-rw
-r--r-- 1 kuangfangjun root 437 Sep 5 15:21 decoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.param
-rw-r--r-- 1 kuangfangjun root 159M Sep 5 15:21 encoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.bin
-rw-r--r-- 1 kuangfangjun root 21K Sep 5 15:21 encoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.param
-rw-r--r-- 1 kuangfangjun root 1.5M Sep 5 15:21 joiner_jit_trace-iter-468000-avg-16-pnnx.ncnn.bin
-rw-r--r-- 1 kuangfangjun root 488 Sep 5 15:21 joiner_jit_trace-iter-468000-avg-16-pnnx.ncnn.param
注:上述模型文件,目前还没有使用任何的量化操作。里面存储的都是float32类型的参数
下载中文模型
git lfs install
git clone https://huggingface.co/csukuangfj/sherpa-ncnn-2022-09-30
下载完后,我们可以得到下述文件:

文件大小信息如下:
$ls-lh tokens.txt *ncnn*
-rw-r--r-- 1 kuangfangjun root 5.5M Sep 30 17:26 decoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.bin
-rw-r--r-- 1 kuangfangjun root 439 Sep 30 17:26 decoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.param
-rw-r--r-- 1 kuangfangjun root 159M Sep 30 17:25 encoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.bin
-rw-r--r-- 1 kuangfangjun root 21K Sep 30 17:25 encoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.param
-rw-r--r-- 1 kuangfangjun root 6.5M Sep 30 17:28 joiner_jit_trace-epoch-11-avg-2-pnnx.ncnn.bin
-rw-r--r-- 1 kuangfangjun root 490 Sep 30 17:28 joiner_jit_trace-epoch-11-avg-2-pnnx.ncnn.param
-rw-r--r-- 1 kuangfangjun root 48K Sep 30 17:24 tokens.txt
注:上述模型文件,目前还没有使用任何的量化操作。里面存储的都是float32类型的参数
使用英文模型进行识别
识别一个文件
./build/bin/sherpa-ncnn \
./sherpa-ncnn-2022-09-05/tokens.txt \
./sherpa-ncnn-2022-09-05/bar/encoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-05/bar/encoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-05/bar/decoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-05/bar/decoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-05/bar/joiner_jit_trace-iter-468000-avg-16-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-05/bar/joiner_jit_trace-iter-468000-avg-16-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-05/test_wavs/1089-134686-0001.wav
注:如果你使用的是Windows, 请用./build/bin/Release/sherpa-ncnn.exe。
目前只支持单通道、16 kHz 采样率、.wav格式的音频文件。
利用麦克风进行实时识别
./build/bin/sherpa-ncnn-microphone \
./sherpa-ncnn-2022-09-05/tokens.txt \
./sherpa-ncnn-2022-09-05/bar/encoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-05/bar/encoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-05/bar/decoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-05/bar/decoder_jit_trace-iter-468000-avg-16-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-05/bar/joiner_jit_trace-iter-468000-avg-16-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-05/bar/joiner_jit_trace-iter-468000-avg-16-pnnx.ncnn.bin
注:如果你使用的是Windows, 请用./build/bin/Release/sherpa-ncnn-microphone.exe。
下面是一个bilibili的视频链接,详细演示了如何在macOS系统编译sherpa-ncnn以及如何使用sherpa-ncnn利用麦克风进行实时的语音识别:
https://www.bilibili.com/video/BV1TP411p7dh/
使用中文模型进行识别
识别一个文件
./build/bin/sherpa-ncnn \
./sherpa-ncnn-2022-09-30/tokens.txt \
./sherpa-ncnn-2022-09-30/encoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-30/encoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-30/decoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-30/decoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-30/joiner_jit_trace-epoch-11-avg-2-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-30/joiner_jit_trace-epoch-11-avg-2-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-30/test_wavs/0.wav
注:如果你使用的是Windows, 请用./build/bin/Release/sherpa-ncnn.exe。
目前只支持单通道、16 kHz 采样率、.wav格式的音频文件。
利用麦克风进行实时识别
./build/bin/sherpa-ncnn-microphone \
./sherpa-ncnn-2022-09-30/tokens.txt \
./sherpa-ncnn-2022-09-30/encoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-30/encoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-30/decoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-30/decoder_jit_trace-epoch-11-avg-2-pnnx.ncnn.bin \
./sherpa-ncnn-2022-09-30/joiner_jit_trace-epoch-11-avg-2-pnnx.ncnn.param \
./sherpa-ncnn-2022-09-30/joiner_jit_trace-epoch-11-avg-2-pnnx.ncnn.bin
注:如果你使用的是Windows, 请用./build/bin/Release/sherpa-ncnn-microphone.exe。
下面是一个bilibili的视频链接,详细演示了如何使用sherpa-ncnn在Windows平台利用麦克风进行实时的语音识别:
https://www.bilibili.com/video/BV1214y177vu/
https://github.com/k2-fsa/sherpa-ncnn/blob/master/.github/workflows/arm-linux-gnueabihf.yaml
和
https://github.com/k2-fsa/sherpa-ncnn/blob/master/.github/workflows/arm-linux-gnueabihf.yaml
演示了如何使用qemu-arm和qemu-aarch64运行sherpa-ncnn。
总结
本文详细介绍了如何使用sherpa-ncnn进行语音识别,涉及到模型转换、 模型验证、sherpa-ncnn的安装及使用方法。
目前我们只提供了不带任何语言模型的解码方法,这直接导致了本文开头视频中的出生为人识别成了出声为人。我们下一步工作需要实现结合外部语言模型进行解码(主要是基于 n-gram LM)。
值得指出的是,ncnn只支持batch size == 1。我们正在支持其他的推理框架,如mace[9]。
对Android和iOS等平台的支持,我们需要来自社区的你进行支持。如果你感兴趣,欢迎给sherpa-ncnn提PR,我们有新一代Kaldi周边的 文创产品赠送。
引用链接
[1] sherpa: http://github.com/k2-fsa/sherpa[2] onnx: https://github.com/onnx/onnx[3] ncnn: https://github.com/Tencent/ncnn[4] PNNX: https://github.com/Tencent/ncnn/tree/master/tools/pnnx[5] sherpa-ncnn: https://github.com/k2-fsa/sherpa-ncnn[6] LSTM transducer: https://github.com/k2-fsa/icefall/tree/master/egs/librispeech/ASR/lstm_transducer_stateless2[7] icefall: https://github.com/k2-fsa/icefall/[8] icefall: https://github.com/k2-fsa/icefall/[9] mace: https://github.com/XiaoMi/mace
