本文介绍新一代 Kaldi[1] 服务端框架 sherpa[2] 中 近期添加的非流式语音识别 C++ API。

简介

虽然 sherpa 的底层实现如神经网络计算和解码等都是基于 C++, 但 sherpa 一直只提供 Python 接口给用户使用。Python 接口的优点在于它能够 降低用户使用 sherpa 的门槛,方便用户修改和扩展 sherpa
近来有部分用户呼吁 sherpa 应该提供 C++ 接口,以便把 sherpa 与现有的基于 C++ 的产线系统相结合,如 https://github.com/k2-fsa/sherpa/issues/85 和 https://github.com/k2-fsa/sherpa/issues/101 都希望 sherpa 提供 C++ 接口。
作为回应,我们最近着手在 sherpa 中添加 C++ 接口。截至目前,我们已经完成了 基于 RNN-T 的非流式语音识别 C++ 接口。 流式识别的接口仍在开发中,将于近期 与大家见面。
小编注:如果大家有什么需求,请在新一代 Kaldi 对应的 repo 中提 issue, 详细 描述你需要的功能。
非流式接口目前提供如下功能:
  • • 给定音频对应的文件名进行识别
  • • 输入 audio samples 进行识别
  • • 输入 fbank 特征进行识别
为了方便大家使用,我们提供一个 .cc 文件,编译成 binary 之后,通过命令行 调用,可以支持:
  • • 提供一个文件名进行解码
  • • 提供多个文件名进行解码
  • • 对 wav.scp 进行解码。对的,就是 Kaldi里面的 wav.scp
  • • 对 feats.scp 进行解码。
值得指出的是,上述功能均支持以 batch 的方式进行识别,并支持 CPU 和 GPU。
同时,我们也提供预训练模型供大家下载。详细的使用方法,可以参考 sherpa 的使用文档:https://k2-fsa.github.io/sherpa/cpp/offline_asr/index.html
接下来我们介绍非流式 C++ 接口的安装使用

安装

sherpa 对 Linux,macOS 和 Windows 这三个平台都有良好的支持。在上述三个平台上,可以通过编译源代码的方式安装 sherpa。针对 Linux 和 Windows, 我们还支持通过 conda install 安装 sherpa

使用 conda install 安装 sherpa

小编注:此种方法仅支持 CPU 版本的 sherpa, 并且仅限于 Linux 和 Windows。
为了方便用户安装 sherpa,我们事先把 sherpa 及其依赖都编译好,并托管在 https://anaconda.org/ 上。用户只需下面一条命令即可安装 sherpa 及其依赖:
conda install \

  -c k2-fsa \

  -c k2-fsa-sherpa \

  -c kaldifeat \

  -c kaldi_native_io \

  -c pytorch \

  cpuonly \

  k2 \

  sherpa \

  kaldifeat \

  kaldi_native_io \

  pytorch=1.12.0 \

  python=3.8
或者写成单独一行:
conda install -c k2-fsa -c k2-fsa-sherpa -c kaldifeat -c kaldi_native_io -c pytorch cpuonly k2 sherpa kaldifeat kaldi_native_io pytorch=1.12.0 python=3.8
请务必别漏掉上面的 -c 选项。你可以交换 -c 的顺序,但是他们一个都不能少
用户可以选择不同的 PyTorch 版本和对应的 Python 版本。可以使用如下命令查看 sherpa 针对哪些版本的 PyTorch 和 Python 提供了预编译版本。
conda search -c k2-fsa-sherpa sherpa
部分输出如下所示(以 Linux 为例):
Loading channels: 
done
# Name                       Version           Build  Channel
sherpa                         0.7.1 cpu_py3.8_torch1.9.0  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.8_torch1.9.1  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.9_torch1.10.0  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.9_torch1.10.1  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.9_torch1.10.2  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.9_torch1.11.0  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.9_torch1.12.0  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.9_torch1.12.1  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.9_torch1.8.1  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.9_torch1.9.0  k2-fsa-sherpa

sherpa                         0.7.1 cpu_py3.9_torch1.9.1  k2-fsa-sherpa
如果上述输出中,找不到你希望的 PyTorch 和 Python 的组合,我们建议从源码安装 sherpa

查看是否成功安装 sherpa

为了检查是否成功安装 sherpa,可以在命令行里面输入:
sherpa-version
sherpa --help
由于 Windows 平台不支持在 executable 里面设置 runtime path。在运行 上述两条检查命令之前,你还需要设置如下环境变量
set
 path=%conda_prefix%\lib\site-packages\sherpa\bin;%path%

set
 path=%conda_prefix%\lib\site-packages\torch\lib;%path%

从源码安装 sherpa

从源码安装 sherpa 之前,用户需要先安装 k2。请参考 https://k2-fsa.github.io/k2/installation/index.html
安装好 k2 之后,还需要使用下述命令安装 sherpa 的另外 两个依赖:
pip install -U kaldifeat kaldi_native_io
小编注: kaldifeat 用于计算 fbank 特征;kaldi_native_io 用于 读取 wav.scp 和 feats.scp
所有的依赖都安装好之后,我们可以用如下命令安装 sherpa
git 
clone
 https://github.com/k2-fsa/sherpa

cd
 sherpa

python3 setup.py bdist_wheel
以 Linux 为例,上述命令执行完后,你可以在 ./dist 目录里面找到一个文件名类似 k2_sherpa-0.7.1-cp38-cp38-linux_x86_64.whl 的文件。然后可以使用
pip install ./dist/k2_sherpa-0.7.1-cp38-cp38-linux_x86_64.whl
安装 sherpa。安装完之后,你就可以在命令行里面执行
sherpa-version
sherpa --help
小编注:你也可以使用如下方式,从源码安装 sherpa
git 
clone
 https://github.com/k2-fsa/sherpa

cd
 sherpa

mkdir
 build

cd
 build

cmake -DCMAKE_BUILD_TYPE=Release ..

make -j
执行完后,你可以在 ./bin 目录下,找到 sherpa-version 和 sherpa 这两个可执行程序。

使用

以 Linux 为例。文中的描述,对 macOS 和 Windows 也是适用的。
安装好 sherpa 之后,接下来我们介绍使用 sherpa 的两种方式:
  • • (1)使用生成的 executable sherpa
  • • (2)如何在 C++ 代码中, 使用相应的头文件 sherpa/cpp_api/offline_recognizer.h 和 链接生成的动态库 libsherpa_offline_recognizer.so

使用生成的 executable sherpa

sherpa --help
可以查看 sherpa 的帮助。目前它支持以下四种功能:
  • • (1)指定一个音频路径进行解码
  • • (2)指定多个音频路径,以 batch 的方式进行解码
  • • (3)给定 wav.scp 进行解码
  • • (4)给定 feats.scp 进行解码
下面我们以 WenetSpeech[3] 预训练模型为例,向各位读者展示上面四种功能。

下载预训练模型

开始实验之前,我们先下载使用 icefall[4] 在 WenetSpeech[5] 数据集上训练好的模型:
sudo apt-get install git-lfs

git lfs install

git 
clone
 https://huggingface.co/luomingshuang/icefall_asr_wenetspeech_pruned_transducer_stateless2

(1)指定一个音频路径进行解码

使用上面下载的模型,对一个音频文件进行识别的命令为
nn_model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt

tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt


wav1=./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000000.wav


sherpa \

  --nn-model=
$nn_model
 \

  --tokens=
$tokens
 \

  --use-gpu=
false
 \

  --decoding-method=greedy_search \

$wav1
输出结果如下所示:
[I] /usr/share/miniconda/envs/sherpa/conda-bld/sherpa_1661003501349/work/sherpa/csrc/parse_options.cc:495:int sherpa::ParseOptions::Read(int, const char* const*) 2022-08-20 23:06:09 sherpa --nn-model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt --tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt --use-gpu=
false
 --decoding-method=greedy_search ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000000.wav


[I] /usr/share/miniconda/envs/sherpa/conda-bld/sherpa_1661003501349/work/sherpa/csrc/sherpa.cc:126:int main(int, char**) 2022-08-20 23:06:10

--nn-model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt

--tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt

--decoding-method=greedy_search

--use-gpu=
false

[I] /usr/share/miniconda/envs/sherpa/conda-bld/sherpa_1661003501349/work/sherpa/csrc/sherpa.cc:270:int main(int, char**) 2022-08-20 23:06:11

filename: ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000000.wav

result: 对我做了介绍那么我想说的是呢大家如果对我的研究感兴趣呢
小编注:你需要提供的内容有:(a)模型 --nn-model。(b)符号表 --tokens。(c)解码方法 --decoding-method。(d)是否使用 GPU --use-gpu
sherpa --help 可以查看详细的说明。

(2)指定多个音频路径,以 batch 的方式进行解码

若要同时识别多条音频,可以使用如下命令:
nn_model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt

tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt


wav1=./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000000.wav

wav2=./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000001.wav

wav3=./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000002.wav


sherpa \

  --nn-model=
$nn_model
 \

  --tokens=
$tokens
 \

  --use-gpu=
false
 \

  --decoding-method=greedy_search \

$wav1
 \

$wav2
 \

$wav3
是的,你只要在第一条音频后面,继续添加更多的文件就可以了。
上述命令的输出如下所示:
[I] /usr/share/miniconda/envs/sherpa/conda-bld/sherpa_1661003501349/work/sherpa/csrc/parse_options.cc:495:int sherpa::ParseOptions::Read(int, const char* const*) 2022-08-20 23:07:05 sherpa --nn-model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt --tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt --use-gpu=
false
 --decoding-method=greedy_search ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000000.wav ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000001.wav ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000002.wav


[I] /usr/share/miniconda/envs/sherpa/conda-bld/sherpa_1661003501349/work/sherpa/csrc/sherpa.cc:126:int main(int, char**) 2022-08-20 23:07:06

--nn-model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt

--tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt

--decoding-method=greedy_search

--use-gpu=
false

[I] /usr/share/miniconda/envs/sherpa/conda-bld/sherpa_1661003501349/work/sherpa/csrc/sherpa.cc:284:int main(int, char**) 2022-08-20 23:07:07

filename: ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000000.wav

result: 对我做了介绍那么我想说的是呢大家如果对我的研究感兴趣呢


filename: ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000001.wav

result: 重点想谈三个问题首先呢就是这一轮全球金融动荡的表现


filename: ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000002.wav

result: 深入地分析这一次全球金融动荡背后的根源

(3)给定 wav.scp 进行解码

首先我们用下面的命令,先创建一个 wav.scp 文件:
cat > wav.scp <<
EOF

wav0 ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000000.wav

wav1 ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000001.wav

wav2 ./icefall_asr_wenetspeech_pruned_transducer_stateless2/test_wavs/DEV_T0000000002.wav

EOF
然后可以用下面命令,对创建的 wav.scp 进行识别:
nn_model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt

tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt


sherpa \

  --nn-model=
$nn_model
 \

  --tokens=
$tokens
 \

  --use-gpu=
false
 \

  --decoding-method=greedy_search \

  --use-wav-scp=
true
 \

  scp:wav.scp \

  ark,scp,t:results.ark,results.scp
输出 log 如下:
[I] /usr/share/miniconda/envs/sherpa/conda-bld/sherpa_1661003501349/work/sherpa/csrc/parse_options.cc:495:int sherpa::ParseOptions::Read(int, const char* const*) 2022-08-20 23:10:01 sherpa --nn-model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt --tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt --use-gpu=
false
 --decoding-method=greedy_search --use-wav-scp=
true
 scp:wav.scp ark,scp,t:results.ark,results.scp


[I] /usr/share/miniconda/envs/sherpa/conda-bld/sherpa_1661003501349/work/sherpa/csrc/sherpa.cc:126:int main(int, char**) 2022-08-20 23:10:02

--nn-model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt

--tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt

--decoding-method=greedy_search

--use-gpu=
false
用下面的命令可以查看识别结果:
$ cat results.ark


wav0 对我做了介绍那么我想说的是呢大家如果对我的研究感兴趣呢

wav1 重点想谈三个问题首先呢就是这一轮全球金融动荡的表现

wav2 深入地分析这一次全球金融动荡背后的根源
小编注:如果你已经有来自 Kaldi 生成的 wav.scp,那么你可以很方便的用 这种方式对他们进行解码。
可以使用 --batch-size 这个选项,来调整解码时使用的 batch size。

(4)给定 feats.scp 进行解码

如果你有事先计算好的特征并保存在 feats.scp 和 feats.ark 里面,那么你可以 用下面的命令对它们进行识别:
nn_model=./icefall_asr_wenetspeech_pruned_transducer_stateless2/exp/cpu_jit_epoch_10_avg_2_torch_1.7.1.pt

tokens=./icefall_asr_wenetspeech_pruned_transducer_stateless2/data/lang_char/tokens.txt


sherpa \

  --nn-model=
$nn_model
 \

  --tokens=
$tokens
 \

  --use-gpu=
false
 \

  --decoding-method=greedy_search \

  --use-feats-scp=
true
 \

  scp:feats.scp \

  ark,scp,t:results.ark,results.scp
注意: 如果你使用的是 icefall 提供的预训练模型,那么你不能使用 Kaldi 生成 的 feats.scp。因为计算特征时,Kaldi 使用的是没有归一化的 audio samples。即 audio samples 的取值范围是 [-32768, 32767]。而 icefall 中采用的范围是 [-1, 1]
我们上面为大家介绍了如何使用生成的 executable sherpa 进行语音识别。接下来我们为大家介绍 如何在 C++ 代码里面使用 sherpa 提供的 C++ API 进行语音识别。

使用 sherpa 提供的 C++ API

安装好 sherpa 后,我们可以在安装目录里面,找到如下两个文件:
  • • (1)头文件: sherpa/cpp_api/offline_recognizer.h
  • • (2)动态库:lib/libsherpa_offline_recognizer.so
下面的代码展示了如何使用 sherpa 的 C++ API 识别给定的音频。
#include<iostream>

#include"sherpa/cpp_api/offline_recognizer.h"

intmain
(
int
 argc, 
char
 *argv[]) {

if
 (argc < 
4
) {

    std::cerr << 
"Usage: ./test_decode_files /path/to/nn_model "
"/path/to/tokens.txt foo.wav [bar.wav [foobar.wav] ... ]\n"
;

exit
(EXIT_FAILURE);

  }

  std::string nn_model = argv[
1
];

  std::string tokens = argv[
2
];

float
 sample_rate = 
16000
;

bool
 use_gpu = 
false
;


  sherpa::DecodingOptions opts;

  opts.method = sherpa::kGreedySearch;

  sherpa::OfflineRecognizer 
recognizer
(nn_model, tokens, opts, use_gpu,

                                       sample_rate);


if
 (argc == 
4
) {

    std::cout << 
"Decode single file\n"
;

auto
 result = recognizer.
DecodeFile
(argv[
3
]);

    std::cout << argv[
3
] << 
"\n"
 << result.text << 
"\n"
;

return0
;

  }


  std::cout << 
"Decode multiple files\n"
;


  std::vector<std::string> filenames;

for
 (
int
 i = 
3
; i != argc; ++i) {

    filenames.
push_back
(argv[i]);

  }


auto
 results = recognizer.
DecodeFileBatch
(filenames);

for
 (
size_t
 i = 
0
; i != filenames.
size
(); ++i) {

    std::cout << filenames[i] << 
"\n"
 << results[i].text << 
"\n\n"
;

  }

return0
;

}
假设上述代码保存在文件 test_decode_files.cc 中,下面的 Makefile 展示了 如何编译和链接:
sherpa_install_dir := 
$(shell python3 -c 'import os; import sherpa; print(os.path.dirname(sherpa.__file__)
)')

sherpa_cxx_flags := 
$(shell python3 -c 'import os; import sherpa; print(sherpa.cxx_flags)
')


$(info sherpa_install_dir: $(sherpa_install_dir))
$(info sherpa_cxx_flags: $(sherpa_cxx_flags))

CXXFLAGS := -I
$(sherpa_install_dir)
/
include
CXXFLAGS += -Wl,-rpath,
$(sherpa_install_dir)
/lib

CXXFLAGS += 
$(sherpa_cxx_flags)
CXXFLAGS += -std=c++14


LDFLAGS := -L 
$(sherpa_install_dir)
/lib -lsherpa_offline_recognizer


$(info CXXFLAGS: $(CXXFLAGS))
$(info LDFLAGS: $(LDFLAGS))

test_decode_files: test_decode_files.cc
$(CXX)$(CXXFLAGS)
 -o 
$@$<$(LDFLAGS)

.PHONY: clean
clean:
$(RM)
 test_decode_files
由于使用了 runtime path,在 Linux 和 macOS 平台上,生成的 executable test_decode_files 可以直接运行,无须设置 LD_LIBRARY_PATH 环境变量。
小编注:上面的 API 只展示了如何识别音频文件。我们其实还支持输入 audio samples 和特征进行解码。更多的列子,可以参考:
  • • https://github.com/k2-fsa/sherpa/blob/master/sherpa/cpp_api/test_decode_features.cc
  • • https://github.com/k2-fsa/sherpa/blob/master/sherpa/cpp_api/test_decode_samples.cc

总结

本文介绍了如何安装 sherpa 和如何使用 sherpa 提供的 C++ 接口进行语音识别。支持的输入类型有音频文件、wav.scp 和 feats.scp 等。
目前我们只提供了非流式的 C++ 接口,流式的 C++ 接口仍在开发中。

What's next

目前 C++ 的实现还依赖 PyTorch, 我们后续会提供不依赖 PyTorch 的 C++ 实现。敬请关注 sherpa 的开发进展。

往期文章

本文出品:新一代 Kaldi-NGK 编辑部
撰文:蛋哥的 csukuangfj

引用链接

[1]
 Kaldi: 
https://github.com/kaldi-asr/kaldi
[2]
 sherpa: 
https://github.com/k2-fsa/sherpa
[3]
 WenetSpeech: 
https://github.com/wenet-e2e/WenetSpeech
[4]
 icefall: 
https://github.com/k2-fsa/icefall
[5]
 WenetSpeech: 
https://github.com/wenet-e2e/WenetSpeech
继续阅读
阅读原文