本文介绍新一代Kaldi[1]服务端框架sherpa[2]中 近期添加的非流式语音识别 C++ API。
来源丨新一代kaldi
简介
虽然 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%
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 < 然后可以用下面命令,对创建的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
#include "sherpa/cpp_api/offline_recognizer.h"
int main(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";
return 0;
}
std::cout << "Decode multiple files\n";
std::vector 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";
}
return 0;
} 假设上述代码保存在文件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.cchttps://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:https://github.com/kaldi-asr/kaldi
sherpa:https://github.com/k2-fsa/sherpa
WenetSpeech:https://github.com/wenet-e2e/WenetSpeech
icefall:https://github.com/k2-fsa/icefall
WenetSpeech:https://github.com/wenet-e2e/WenetSpeech
