kaldi中的在线解码器

翻译:[email protected]

时间:2015年5月

本文档将介绍kaldi里在线解码。

在线解码,我们的意思是实时地处理特征来解码,和我们不需要等待整个音频都获取后再解码。(我们不使用实时解码这个词,主要原因是实时解码经常用在那些跟实时不相上下的解码中,即使它在批处理模式中经常使用)。

在kaldi最初的几年里,我们仅仅考虑离线识别的方法,为了尽可能快地达到先进水平,我们现在尽最大的努力来支持在线解码。

这里有两种在线解码的例子:一个是旧的在线解码,在目录online/和onlinebin/,和新的解码例子,在目录online2/和online2bin/。这个旧的在线解码已经被弃用,它们最终将在trunk中移除(但会在.../branches/complete里保留)。

对于旧的解码例子,这里有一些文档,但是我们建议先读这页。

Scope of online decoding in Kaldi

在kaldi中,我们的目标是为在线解码提供类似于库函数的接口。也就是说,我们为其提供函数特性的接口而不是命令行的形式。原因是,根据数据的获取和传输,不同的人的需求是不同的。在旧的在线解码中,我们提供在UDP上传输数据的接口和类似的解码接口,但是在新的在线解码里,我们的目标仅仅是去演示内部的代码,和从现在开始我们不提供任何样例程序,你可以根据情况挂接到你真实的实时音频采集中,你可以用你自己的方式来做。

我们这里为基于GMM模型提供了解码程序(看下一部分)和对于神经网络模型也提供了解码程序(你可以看 Neural net based online decoding with iVectors).

online decoding

程序online2-wav-gmm-latgen-faster.cc是目前基于GMM模型的在线解码的最原始样例程序。它读取整个wav文件,但是在内部是逐块来处理的。在样例脚本egs/rm/s5/local/online/run_gmm.sh中,你可以看到一个样例脚本是如何利用这个程序来建立模型和评估的。这个程序的主要目的就是在一个典型的批处理框架下使用基于GMM的在线解码,这样你可以很简单的评估字错误率。我们计划对SGMMs和DNNs添加相同的程序。事实上为了做在线解码,你可以修改这个程序。你应该注意到(和这个对于语音识别的人是明显的,但对于其他的人就不是)音频的采样率应该跟你训练使用的音频的采样率匹配(和过采样将不行,但是下采样可以)。

Decoders versus decoding programs

在Kaldi中,当我们使用解码这个词,我们一般不是指整个解码程序。我们指的是内部的解码对象,是类型LatticeFasterDecoder的一种。整个对象使用解码图(一个FST),和解码对象(看The Decodable interface)。所有的解码器一般都支持在线解码;只不过解码程序里的代码需要去修改。我们需要注意的是,在线解码器调用decoder时的一些不同就行。

  • 在旧的在线解码中(在online/),如果"decoder"是一些decoder (比如:类型LatticeFasterDecoder)和"decodable"是一个decodable对象的一个合适类型,你需要调用decoder.Decode(&decodable),和当输入完成了,这个调用将阻塞(因为decoder调用了decodable.IsLastFrame(),而这个会阻塞)。
  • 在新的在线解码中(在online2/),你将调用decoder.InitDecoding(),和然后每次你得到更多的特征数据时,你将调用decoder.AdvanceDecoding()。对于离线使用,你将调用Decode()。

我们需要注意的是,在旧的在线解码中,有一个decoder调用OnlineFasterDecoder。从这个名字可以看出,这个唯一的一个支持在线解码的decoder。这个OnlineFasterDecoder最特殊的是它有能力去推断哪些字是接下来需要解码的,但是不需要知道未来的音频数据是什么。 所以你可以输出这些单词。这个对于在线转写来说是非常重要的,和如果这是个需求的话,我们也许将目录online/的decoder移到目录decoder/和使得对新的在线解码是兼容的。

Feature extraction in online decoding

在线解码中的绝大部分复杂度是与特征提取和自适应相关的。

online-feature.h里,我们提供了特征提取的各种成分的类,所有的都是从类OnlineFeatureInterface衍生出来的。OnlineFeatureInterface是在线特征提取的一个基类。这个接口指定这个对象如何把特征给调用者(OnlineFeatureInterface::GetFrame())和或者已经准备了多少帧的特征(OnlineFeatureInterface::NumFramesReady()),但是不能知道是怎么获得这些特征的。这个取决于子类。

online-feature.h中,我们定义了类OnlineMfcc和OnlinePlp,它们都是底层的特征。它们都有一个成员函数OnlineMfccOrPlp::AcceptWaveform(),当获取到数据时,使用者可以调用。在online-feature.h 里的所有其他在线特征类型是"derived"特征,所以在它们的构造函数中采用了一个对象OnlineFeatureInterface和通过一个存储指针指向这个对象来获取它们的输入特征。

online-feature.h中的在线特征提取代码,唯一不繁琐的部分就是the cepstral mean and variance normalization (CMVN) (和注意fMLLR,或者线性变换,和估计都不是繁琐的,复杂度在其他地方)。接下来我们将介绍CMVN。

Cepstral mean and variance normalization in online decoding

倒谱均值归一化是使用原始数据减去数据平均值(一般是MFCC特征数据)的归一化方法。"Cepstral"仅仅指的是正常的特征类型;在MFCC中的第一个C的意思就是"Cepstral",倒谱是对数频谱的逆傅里叶变换,尽管这里我们一般使用余弦变换。无论任何方式,在倒谱方差归一化中,每个特征维度被尺度化,以使得它的方差为1。在所有的当前脚本里,我们关掉倒谱方差归一化和仅仅使用倒谱均值归一化,但是相同的代码都可以处理它们。在接下来的讨论中,为了简化,我们仅仅讨论倒谱均值归一化。

在Kaldi的脚本中,倒谱均值和方差归一化(CMVN)一般是对每一个说话人来做的。明显地,在一个在线解码的例子中,这个是不可能做的,因为它是非因果的(当前的特征依赖于未来的特征)。

我们的基本解决方案就是使用滑动窗来做倒谱均值归一化。我们在一个滑动窗上,默认的是6秒,来计算均值(看目录online2bin/里程序的选项"--cmn-window",默认设置为600)。为这个计算的选项类OnlineCmvnOptions有一些额外的配置变量,speaker-frames (default: 600),和global-frames (default: 200)。这些指定了我们将从相同的说话人中怎么利用这些先验的信息,或者倒谱的一个广义平均,来改善每句话的一开始几秒的估计。程序apply-cmvn-online可以把这个归一化作为训练的一部分,以至于我们可以在匹配的特征上进行训练。

Freezing the state of CMN

类OnlineCmvn有函数GetState和SetState,这就使得可以跟踪说话人间的CMVN的的状态。它也有一个函数Freeze()。这个函数可以把倒谱均值归一化的状态设置为一个特定的值,以至于在调用这个函数Freeze()之后,,甚至更早些时候,当用户调用Freeze(),调用GetFrame()将应用我们使用的均值offset。通过调用函数GetState和SetState,这个冻结状态将传播到这个说话人的未来的语句中。我们这么做的原因是我们不认为在一个经常变化的CMN offset上用 fMLLR做说话人自适应是有意义的。所以当我们开始估计fMLLR(看接下来的),我们将冻结CMN状态和在未来让他固定。我们冻结的时刻,CMN的值不是特别重要的,因为fMLLR包含CMN。我们冻结CMN状态到一个特定值的原因,不是当我们开始估计fMLLR仅仅跳过CMN,而是当我们可以增量的估计这个参数时使用了一种叫basis-fMLLR的方法,,和它对offsets来说不是完全不变的。

Adaptation in online decoding

在语音识别中使用的最标准的自适应方法是feature-space Maximum Likelihood Linear Regression (fMLLR),在我们的书籍上称为受限制的MLLR (CMLLR),但是我们在kaldi的代码和文档里使用词语fMLLR。fMLLR是由特征的一个仿射(linear + offset)变换构成的;参数的数目是d * (d+1),这个d是最终的特征维度(一般为40)。当解码更多数据时,在线解码需要使用一个基本的方法来增量估计不断增加的变换参数。在解码层的最高层逻辑大多通过类SingleUtteranceGmmDecoder来实现。

fMLLR估计不是连续去做的,而是分阶段去做的,因为它涉及到计算词图后验概率和这个使用连续的方式是不容易去做的。在类 OnlineGmmDecodingAdaptationPolicyConfig里的配置变量决定了我们数目时候去重新估计fMLLR。默认的情况是,在第一个语句中,在2秒后开始估计,和此后以1.5倍的速度几何级数的增长(所以在2秒,3秒,4.5秒...)。对于后面的语句,我们在5秒,10秒,20秒等等的时候估计它。对于所有的语句,我们在语句的末尾估计它。

注意CMN适应的状态是冻结的,就像我们上面提到的那样,我们为一个说话人第一次估计fMLLR时,默认的是在第一个语句的2秒。

Use of multiple models in GMM-based online decoding

在online-gmm-decoding.h中为GMM的在线解码中,有三个模型可以被使用。它们都是类OnlineGmmDecodingModels,如果很少的模型被提供时,你需要根据一定的逻辑来使用不同目的的模型。这三个模型分别为:

  • 说话人独立模型,使用apply-cmvn-onlin.cc apply-cmvn-online来训练得到一个在线模式的CMVN
  • 说话人自适应模型,使用fMLLR训练得到
  • 说话人自适应模型的区分性训练版本。我们这里使用最大似然估计模型来估计自适应的参数,这样比使用一个区分性训练模型更能与最大似然框架保持一致,尽管这个区别很小和你使用区分性训练模型可能损失的信息很少(和节约一些内存)。

Neural net based online decoding with iVectors

我们最好的在线解码样例,我们推荐使用的就是基于神经网络的解码器。适应的哲学就是给你未适应的神经网络和没有均值归一化的特征(在我们的样例中是MFCCs),和给他一个iVector。一个iVector 是一个具有几百维度的向量(在这个特殊的例子里,是一二百),它表示说话人的特性。为了了解更多的信息,你可以去看说话人识别文献。我们的意思就是iVector可以给我们需要知道的说话人的信息。这个被证明是有用的。iVector的估计是以从左向右的方式,也就是说在某个时间t上,你可以看到从时间0到t上的输入。它可以看到这个说话人的之前的语句信息。iVector的估计是通过最大似然来计算的,涉及到混合高斯模型。

如果pitch被使用(比如:对于有声调的语言),为了简化这个事情,在iVector估计中我们没有把它包含在特征里;我们仅仅把它包含在神经网络的特征中。对于有声调的语言,我们目前还有没有在线神经网络解码的样例脚本;它还在调试中。

在我们的在线解码的样例例子中的神经网络是p-norm神经网络,一般在多GPUs并行训练中使用。对于不同的样例,我们有一些样例脚本,比如:egs/rm/s5,egs/wsj/s5,egs/swbd/s5b,和egs/fisher_english/s5。最顶层的样例脚本一般是local/online/run_nnet2.sh。在RM数据集上,也使用脚本local/online/run_nnet2_wsj.sh。 这就演示了如何使用一个大的神经网络来训练相同采样率的其他数据库的语音(在我们的例子中是WSJ),和再原来的数据上重新训练。用这种方式,我们在RM上获得了最好的结果。

现在我们已为这个例子添加区分性训练的样例脚本。

Example for using already-built online-nnet2 models

这部分我们将解释如何从www.kaldi-asr.org下载已经训练好的 online-nnet2模型和使用自己的数据来评估。

你可以从http://kaldi-asr.org/downloads/build/2/sandbox/online/egs/fisher_english/s5里下载模型和相关的文件,它是由fisher_english样例来建立的。为了使用online-nnet2模型,你仅仅需要下载2个目录:exp/tri5a/graph和exp/nnet2_online/nnet_a_gpu_online。使用下面的命令来下载这些文件:

wget http://kaldi-asr.org/downloads/build/5/trunk/egs/fisher_english/s5/exp/nnet2_online/nnet_a_gpu_online/archive.tar.gz -O nnet_a_gpu_online.tar.gz
wget http://kaldi-asr.org/downloads/build/2/sandbox/online/egs/fisher_english/s5/exp/tri5a/graph/archive.tar.gz -O graph.tar.gz
mkdir -p nnet_a_gpu_online graph
tar zxvf nnet_a_gpu_online.tar.gz -C nnet_a_gpu_online
tar zxvf graph.tar.gz -C graph

这些文件将被下载到本地目录。我们需要去修改config文件里的路径名,像下面这样:

for x in nnet_a_gpu_online/conf/*conf; do
  cp $x $x.orig
  sed s:/export/a09/dpovey/kaldi-clean/egs/fisher_english/s5/exp/nnet2_online/:$(pwd)/: < $x.orig > $x
done

接下来,选择一个单wav文件来解码。你可以从这里下载一个文件:

 wget http://www.signalogic.com/melp/EngSamples/Orig/ENG_M.wav

这是一个采样率为8kHz的音频文件(不幸的是它是英国英语,所以正确率不是那么好)。可以使用接下来的命令来解码:

~/kaldi-online/src/online2bin/online2-wav-nnet2-latgen-faster --do-endpointing=false \
    --online=false \
    --config=nnet_a_gpu_online/conf/online_nnet2_decoding.conf \
    --max-active=7000 --beam=15.0 --lattice-beam=6.0 \
    --acoustic-scale=0.1 --word-symbol-table=graph/words.txt \
   nnet_a_gpu_online/smbr_epoch2.mdl graph/HCLG.fst "ark:echo utterance-id1 utterance-id1|" "scp:echo utterance-id1 ENG_M.wav|" \
   ark:/dev/null

我们添加了选项–online=false,因为它可以稍微改善了结果。你可以在log文件里看到结果(尽管有其他的方式来检索这个)。对于我们来说,这个log文件的输出如下:

/home/dpovey/kaldi-online/src/online2bin/online2-wav-nnet2-latgen-faster --do-endpointing=false --online=false --config=nnet_a_gpu_online/conf/online_nnet2_decoding.conf --max-active=7000 --beam=15.0 --lattice-beam=6.0 --acoustic-scale=0.1 --word-symbol-table=graph/words.txt nnet_a_gpu_online/smbr_epoch2.mdl graph/HCLG.fst 'ark:echo utterance-id1 utterance-id1|' 'scp:echo utterance-id1 ENG_M.wav|' ark:/dev/null 
LOG (online2-wav-nnet2-latgen-faster:ComputeDerivedVars():ivector-extractor.cc:180) Computing derived variables for iVector extractor
LOG (online2-wav-nnet2-latgen-faster:ComputeDerivedVars():ivector-extractor.cc:201) Done.
utterance-id1 tons of who was on the way for races two miles and then in nineteen ninety to buy sodas sale the rate them all these to commemorate columbus is drawn into the new world five hundred years ago on the one to the moon is to promote the use of so the sales in space exploration 
LOG (online2-wav-nnet2-latgen-faster:main():online2-wav-nnet2-latgen-faster.cc:253) Decoded utterance utterance-id1
LOG (online2-wav-nnet2-latgen-faster:Print():online-timing.cc:51) Timing stats: real-time factor for offline decoding was 1.62102 = 26.7482 seconds  / 16.5009 seconds.
LOG (online2-wav-nnet2-latgen-faster:main():online2-wav-nnet2-latgen-faster.cc:259) Decoded 1 utterances, 0 with errors.
LOG (online2-wav-nnet2-latgen-faster:main():online2-wav-nnet2-latgen-faster.cc:261) Overall likelihood per frame was 0.230575 per frame over 1648 frames.