kaldi中特征变换

简介

Kaldi 目前支持多种特征和模型空间的变换与映射。特征空间变换和映射通过工具来处理(本质上就是矩阵),以下章节将介绍:

变换,投影或者其他没有特定说话人的特征操作包括:

  • 线性判别性分析(LDA)
  • 帧拼接和差分特征计算
  • 异方差的线性判别性分析(HLDA)
  • 全局半协方差/最大似然线性变换

全局变换主要通过说话人自适应的方式应用:

  • 全局GMLLR/fMLLR变换
  • 线性声道长度归一化
  • 指数变换
  • 谱均值和方差归一化

下面我们将介绍回归树以及用到回归树的变换:

  • 为自适应构建回归类树

全局线性或仿射特征变换

由于全局特征空间变换和映射与类不相关(例如语音/静音或者回归类),我们可以将他们表示成矩阵。一个线性变换或者投影由一个矩阵表示,这个矩阵用于左乘一个特征矩阵,则变换后的特征为 .一个仿射变换或者投影也可以相同方式表示。但是我们假设一个1被附加到特征向量,所以变换后的特征为 ,其中, A 是线性变换,b是偏移常量. 注意这个转换与一些文献不同之处在于这里1位于向量最后一维而不是第一维。全局变换和映射通常作为 Matrix<BaseFloat> 类型写到一个文件,而说话人或者句子相关的变换和映射则以说话人id和句子id为索引存储在这样的矩阵表(see The Tableconcept)中。

变换可以通过程序transform-feats应用于特征上,语法如下

 transform-feats <transform><input-feats> <output-feats>

其中<input-feats>是 rspecifier, <output-feats> 是wspecifier, <transform>可能是 rxfilename 或者 rspecifier (see Specifying Tableformats: wspecifiers and rspecifiers and Extendedfilenames: rxfilenames and wxfilenames).这个程序通常用于管道的一部分。 这个程序将根据矩阵的列数与特征维数相等还是比维数多1来确定变换是线性或者仿射变换,一个典型的例子是:

 feats="ark:splice-featsscp:data/train.scp ark:- |

         transform-feats $dir/0.mat ark:- ark:-|"

 some-program some-args "$feats"some-other-args ...

这里文件0.mat包括一个矩阵。一个说话人相关的变换的例子是:

 feats="ark:add-deltas scp:data/train.scpark:- |

  transform-feats --utt2spk=ark:data/train.utt2spk ark:$dir/0.trans ark:-ark:-|"

 some-program some-args "$feats"some-other-args ...

一个句子单元的例子跟上面的例子类似,只需要将–utt2spk 去除即可。这个例子中归档文件0.trans 将包含以说话人id为索引的变换(例如CMLLR变换)。文件data/train.utt2spk将包含多行 "utt-id spk-id" (详见下一节). 程序transform-feats 并不关心变换矩阵是怎么估计的,它只将它应用到特征上. 当它处理完所有特征,它将打印平均每帧的log行列式。这将用于比较目标函数(这个行列式将被加到由gmm-align, gmm-acc-stats, 或gmm-decode-kaldi 打印的帧似然度上)。如果变换的线性部分A(不略偏移部分)不是方阵,则程序将打印的帧平均值 .这将被视作伪对数行列式.将用于验证MLLT估计的收敛性,在MLLT估计中用到的变换矩阵是MLLT矩阵乘以LDA矩阵。

说话人无关vs 说话人 vs 句子

估计变换的程序通常指定来做某种指定的自适应。例如说话人无关或者(指定说话人或者句子)。例如LDA和MLLT/STC变换是说话人无关的,fMLLR变换是指定说话人或者句子的。估计指定说话人或者句子的变换的程序默认情况下将以每句每句的模式工作,但是如果提供了-spk2utt选项的话,将按照每个无关说话人的模式。

接收说话人无关,或者指定说话人或者指定句子的变换的程序是transform-feats.这个程序检测第一个参数是否是rxfilename(see Extended filenames: rxfilenames and wxfilenames)或者rspecifier(see Specifying Table formats: wspecifiers and rspecifiers).如果是前者,将变换视为说话人无关的变换(例如只包含一个矩阵的文件),如果是后者,将有两种选择。如果没有–utt2spk选项提供,将变换视为由句子id为索引的矩阵表,如果该选项提供了(utt2spk是一个以包含说话人id的句子为索引的字符串列表),则这个变换被假设以说话人id为索引,被提供了–utt2spk选项的表将被用来将每个句子映射到说话人id.

句子到说话人 和 说话人到句子 映射

在这里我们将整体介绍一下–utt2spkand –spk2utt两个选项。这两个选项将被处理变换的程序接受,然后他们被用于做每个说话人(相对于每句)的自适应。特别是,处理已经生成好的变换的程序需要–utt2spk选项,而生成变换则需要–spk2utt选项。一个典型的情况是将会有一个类似于下面的名为utt2spk的文件存在:

spk1utt1  spk1
spk1utt2  spk1
spk2utt1  spk2
spk2utt2  spk2
...

这些字符串只是例子,代表通常的说话人和句子的id.同样将会有一个名为spk2utt的文件存在:

spk1 spk1utt1 spk1utt2
spk2 spk2utt1 spk2utt2
...

而且你将提供类似于–utt2spk=ark:some-directory/utt2spk或者–spk2utt=ark:some-directory/spk2utt的选项. 这个'ark:'前缀是必须的因为这些文件是通过Table 以rspecifiers形式提供的,将被理解成包含字符串(或者在spk2utt情况下为字符串矢量)的存档文件.注意utt2spk存档通常以随机访问形式来访问,所以如果你是处理数据的某个子集,安全起见还是提供整个文件为好。但是spk2utt是通过序列方式访问,所以如果你是利用某个数据子集你将需要将spk2utt文件分离开。

接收spk2utt 选项的程序通常会遍历spk2utt文件中的每个说话人id,并为每个speaker-id遍历所有句子,累积每个句子的统计信息。对特征的访问将采用随机访问模式而不是通常的序列模式,这里需要小心设置,因为特征文件非常大,通常完整处理的特征需要从文件读取,这并不允许最节省内存的随机访问,除非仔细设置。(关于这个问题的讨论详见 Avoiding memory bloat when reading archives in random-access mode )为了避免在这种情况下访问特征文件引起内存膨胀,建议为了确保所有文件按utterance-id排序,提供给-spk2utt选项的文件里的句子应该顺序出现,而且对于指定程序的特征输入的rspecifiers需要给出适当的选项("ark,s,cs:-",如果这是标准输入)

组合变换

另一个接收通用变换的程序是compose-transforms。通常的语法是“compose-transformsa b c”,它将执行乘法操作c= a*b,(当然如果a是一个仿射矩阵,将不止是乘法操作)。该脚本的另一个变形是下面的形式:

 feats="ark:splice-feats scp:data/train.scp ark:- |
         transform-feats
           \"ark:compose-transforms ark:1.trans 0.mat ark:- |\"
           ark:- ark:- |"
 some-program some-args "$feats" ...

这个例子也显示了从一个程序调用两层命令。这里0.mat是一个全局变换(例如LDA),而1.tans是以句子id为索引的fMLLR/CMLLR矩阵集合。compose-transforms程序将把多个变换组合起来。通过下列方式这些特征可以更容易计算,但是效率较低:

 feats="ark:splice-feats scp:data/train.scp ark:- |
         transform-feats 0.mat ark:- ark:- |
         transform-feats ark:1.trans ark:- ark:- |"
 ...         

通常情况下,输入到compose-transforms的变换a和b可能要么是说话人无关的,要么是指定说话人和指定句子的变换。如果a是指定句子的,b是指定说话人的,那你需要提供–utt2spk选项。但是指定说话人的a和指定句子的b这样的组合(这没有意义)是不支持的。只要a或者b是表格形式,则组合变换的输出就是表格。出于一致性需要,三个参数a,b and c可能全部代表表格或者文件(例如{r,w}specifiers或者{r,w}xfilenames)

如果a是仿射变换,为了正确执行组合操作,compose-transforms需要知道b是线性还是仿射变换(程序并不知道这个,因为它无法访问到由b转换的特征的维度)。这是通过选项–b-is-affine(bool,默认为false)来控制。如果b是仿射但是你忘了设置该选项,而a是仿射,那么组合变换将通过维度(实际输入的维度)加1把b当做线性变换,而且将输出一个输入维度为真实输入维度加2的一个变换。当该变换应用到特征时,transform-feats将无法理解它,很显然将会出错因为此时维度将会不匹配。

估计变换时的静音加权

消除静音帧对于估计说话人自适应变换例如CMLLR将会有所帮助。对利用多类回归树(参考 Building regression trees for adaptation)的情况也是这样。消除静音帧的方式是通过下调静音音子的后验概率。通过修整 state-levelposteriors来实现.一段bash脚本示例如下 (这个脚本的详细介绍在 Global CMLLR/fMLLR transforms):

ali-to-post ark:$srcdir/test.ali ark:- | \
  weight-silence-post 0.0 $silphones $model ark:- ark:- | \
  gmm-est-fmllr --fmllr-min-count=$mincount \
    --spk2utt=ark:data/test.spk2utt $model "$sifeats" \
   ark,o:- ark:$dir/test.fmllr 2>$dir/fmllr.log

在这里,shell变量“silphones”将被设置为以冒号分隔的、静音音子的整数id列表。

线性判别分析变换

Kaldi 通过类LdaEstimate支持线性判别分析变换(LDA)估计。这个类不需要与任意特殊种类的模型交互,它需要用类的数目进行初始化,累积函数声明如下

class LdaEstimate {
  ...
  void Accumulate(const VectorBase<BaseFloat> &data, int32 class_id,
                  BaseFloat weight=1.0);
};

程序acc-lda 用声学状态(i.e.pdf-ids)作为分类累积LDA统计信息。它需要转移模型用来将对齐(通过transition-ids来表示)映射到概率分布id(pdf-ids),但是它又不受限于任何种类的声学模型。程序est-lda会估计LDA (它读取从acc-lda获得的统计信息). 从变换得到的特征将会有单位方差,但不需要零均值。程序est-lda输出LDA变换矩阵,通过选项–write-full-matrix可以输出没有经过维度下降的全矩阵(它的前面一些行将和LDA映射矩阵相等),用LDA作为HLDA的初始化将很有帮助。

帧拼接

LDA之前通常会对原始MFCC特征先做帧拼接(例如将连续9帧拼接起来),splice-feats程序用来做这个工作。脚本里面典型的一行如下:

feats="ark:splice-feats scp:data/train.scp ark:- |
        transform-feats $dir/0.mat ark:- ark:-|"

"feats"变量后续将会被一些需要读取特征的程序用作rspecifier(c.f. SpecifyingTable formats: wspecifiers and rspecifiers).在这个例子中我们不需要指定需要拼接的帧数,因为我们将使用默认的(–left-context=4,–right-context=4, or一共9帧)

差分特征计算

差分特征计算是通过add-deltas来实现, 它将调用函数ComputeDeltas.差分特征和HTK里面一样有同样默认的初始设置,例如为了计算第一个差分特征我们将特征乘以滑动窗[ -2, 1, 0, 1, 2 ],然后通过除以(2^2 + 1^2 + 0^2 + 1^2 + 2^2 = 10)来归一化. 第二个差分特征通过将这个方法用于第一个差分特征来结算。前后帧数通过参数来控制–delta-window(default: 2),需要添加的差分特征的数目由参数–delta-order (default: 2)控制.利用这个的典型的脚本是:

feats="ark:add-deltas --print-args=false scp:data/train.scp ark:- |"

异方差线性区分性分析(HLDA)

HLDA是一种降低维度的线性特征映射技术,它利用最大似然准则,其中被拒绝的维度利用一个全局的均值和方差来建模,接受的维度利用一个特殊模型来建模,该模型的均值和方差是通过最大似然准则估计得到。目前已经集成到工具里的HLDA的形式是在HldaAccsDiagGmm中实现的。它利用统计信息的一种相对压缩的形式为GMM估计HLDA 。类别对应模型里的高斯分量。由于它没有使用标准的估计方法,我们将在这里解释这个想法。首先,由于内存限制我们不想存储HLDA统计信息的最大形式,那就是每个类的均值和方差。我们观察到如果在HLDA更新过程中我们让方差固定,那么HLDA的估计问题就降低到MLLT(或者全局STC)估计。(See"Semi-tied Covariance Matrices for Hidden Markov Models", by MarkGales, IEEE Transactions on Speech and Audio Processing, vol. 7, 1999, pages272-281, e.g. Equations (22) and (23).)那里用到的统计信息G(ri)也在这里用到了,但是在HLDA情况下,针对接受和拒绝的维度他们需要稍微不同的定义。假设原始特征维度是D,降低后的特征维度为K. 让我们忘掉迭代的上标r, 用为状态使用上标j,为高斯分量利用上标m. 对于接受的维度 ( ), 统计信息如下:

其中 是原始D维空间的高斯均值,  原始K维空间的特征,但是 K维模型里第i维方差。对于被拒绝的方差维度 ( ), 我们用一个单元方差的高斯, 统计信息如下:


其中  是K维空间的全局特征均值. 一旦我们有了这些统计信息, HLDA 估计就同 D维空间的MLLT/STC 类似. 注意所有被拒绝维度的统计信息是相同的, 所以在代码中我们只需要存储K+1 而不是 D 维统计信息

同样,对于程序来说累积K维模型的统计信息也很方便。所以在HLDA累积过程中,我们累积足够估计K维均值的统计信息,对于接受的维度,我们累积下列信息而不是G


对于拒绝的维度 


当然我们只需要存储一个 (e.g. i = K) 因为他们全部都一样. 然后在更新的时候我们可以为维度估计G统计信息 如下:


对维度 ,


其中  是总数,  是全局特征均值.利用跟MLLT同样的方法从G统计信息计算处变换以后, 我们输出变换,并用变换的前面K行将均值映射到K维空间,然后输出变换后的模型。

上面描述的计算量非常大,在每一帧上是 ,K也非常大 (e.g.117). 这是我们为压缩统计付出的代价;如果我们存储全均值和方差统计信息,每一帧的计算量将是 . 为了加速我们有一个可选参数(代码里的"speedup"),将会随机选择一个的帧的子集来计算HLDA统计。例如如果speedup为0.1 我们将在1/10的帧数上统计HLDA统计信息, 如果该选项被激活, 我们需要为均值存储两个不同版本的充分统计信息。一个版本是在子集上统计的均值信息,将只被用于HLDA计算, 对应于上面公式中的 和  。另一个版本的均值信息是在所有训练数据上统计的,用来写输出模型。

整个HLDA的估计过程如下 (seerm_recipe_2/scripts/train_tri2j.sh):

  • 首先利用LDA进行初始化 (我们将同时存储全矩阵和维度下降的矩阵).
  • 开始模型创建和训练过程。在某些我们已经决定做HLDA更新的迭代上(非连续)进行如下操作:
    • 累积HLDA信息 (S, 以及全维度的均值统计信息). 做这些统计的程序(gmm-acc-hlda) 需要利用模型,未转化的特征,当前转换(用来对特征进行变换,为了计算高斯后验概率)
    • 更新HLDA变换。执行该操作的程序 (gmm-est-hlda) 需要模型,统计量,和之前的全转换矩阵,来启动优化和正确地报告辅助函数的变化。它输出新变换(全维度和降低后的维度),以及新估计和转换均值后的模型。

全局半绑定协方差(STC)/最大似然线性变换估计(MLLT)

GlobalSTC/MLLT is a feature-transformation matrix. 全局STC/MLLT是平方特征变换矩阵。详情见"Semi-tiedCovariance Matrices for Hidden Markov Models", by Mark Gales, IEEETransactions on Speech and Audio Processing, vol. 7, 1999, pages 272-281.将它看成特征空间转化,目标函数是给定模型后转换后特征的平均帧似然度加上变换的对数行列式。模型的均值也在更新阶段被变换旋转了。对于维度充分统计量如下,其中D 是特征维度:


见参考文献, Equations (22) and (23) 更新的公式。这基本是一个简单形式的对角线逐行的受限MLLR/fMLLR更新方程,其中二次方程的一阶项消失了。注意我们的实现与参考文献不同在于我们用了矩阵逆的一列而不是辅助因子,因为乘以行列式并不会对结果造成不同而且可能会引起浮点下溢出或者上溢出的潜在问题。

我们描述整个过程类似我们在LDA特征上做MLLT。但是它同样在传统的差分特征上适用。参考脚本里的例子rm_recipe_2/steps/train_tri2f:

  • 估计LDA变换矩阵(我们只需要前面的行,不是整个矩阵)矩阵表示为 .
  • 开始一个正常的模型训练过程,始终利用被转换过的特征. 在某些指定的迭代(更新MLLT矩阵)做如下操作:
    • 在当前全变换的空间累积MLLT统计信息(例如在转化后的特征上).为了效率,我们利用训练数据的子集来做这件事。
    • 进行MLLT更新,产生正方矩阵 .
    • 变换模型均值通过设置.
    • 更新当前变换通过设置 

MLLT估计相关的程序是gmm-acc-mllt和est-mllt。同样也需要程序gmm-transform-means利用转化高斯均值,以及组合变换做乘法

全局CMLLR/fMLLR变换

约束最大似然线性回归CMLLR, 也就是通常说的特征空间MLLR(fMLLR), 是一种形式为 的仿射特征变换,我们用表示,其中是特征向量最后附加1. 注意这与一些文献将1放在向量最前面不同。介绍CMLLR和其估计技术的论文,请参考see"Maximum likelihood linear transformations for HMM-based speechrecognition" by Mark Gales, Computer Speech and Language Vol. 12, pages75-98.

充分统计量如下存储:


其中  是反协方差矩阵, 对于D 是特征维度,


我们的估计方法是标准方法,见附录B (in particular sectionB.1, "Direct method over rows")。不同的是我们用矩阵逆的一列而不是辅助因子,也就是忽略行列式因为它并不影响结果而且可能引起上下溢出的危险。

全局约束MLLR(CMLLR) 的估计是通过类FmllrDiagGmmAccs, 和程序gmm-est-fmllr (同样可参考gmm-est-fmllr-gpost). gmm-est-fmllr的语法如下:

gmm-est-fmllr [options] <model-in> <feature-rspecifier> \
   <post-rspecifier> <transform-wspecifier>

"<post-rspecifier>"对应转化id(transition-id)级别的后验概率(see State-level posteriors).程序将会输出CMLLR变换的表,默认以句子为索引,或者如果–spk2utt选择也提供了的话,就以说话人为索引。

下面是脚本的节选(rm_recipe_2/steps/decode_tri_fmllr.sh),它会利用前一轮没有自适应的解码产生的对齐来估计和应用CMLLR变换。上一轮解码假设是用的相同的模型,否则我们需要利用程序"convert-ali"来转化对齐。

...
silphones=48 # colon-separated list with one phone-id in it.
mincount=500 # min-count to estimate an fMLLR transform
sifeats="ark:add-deltas --print-args=false scp:data/test.scp ark:- |"
 
# The next comand computes the fMLLR transforms.
ali-to-post ark:$srcdir/test.ali ark:- | \
  weight-silence-post 0.0 $silphones $model ark:- ark:- | \
  gmm-est-fmllr --fmllr-min-count=$mincount \
    --spk2utt=ark:data/test.spk2utt $model "$sifeats" \
   ark,o:- ark:$dir/test.fmllr 2>$dir/fmllr.log
 
feats="ark:add-deltas --print-args=false scp:data/test.scp ark:- |
  transform-feats --utt2spk=ark:data/test.utt2spk ark:$dir/test.fmllr
       ark:- ark:- |"
 
# The next command decodes the data.
gmm-decode-faster --beam=30.0 --acoustic-scale=0.08333 \
  --word-symbol-table=data/words.txt $model $graphdir/HCLG.fst \
 "$feats" ark,t:$dir/test.tra ark,t:$dir/test.ali 2>$dir/decode.log

线性VTLN (LVTLN)

最近几年,有大量的论文描述VTLN的实现,事实证明一个线性特征变换与每个VTLN扭曲因子对应。例如, ``UsingVTLN for broadcast news transcription'', by D. Y. Kim, S. Umesh, M. J. F.Gales, T. Hain and P. C. Woodland, ICSLP 2004.

e在这一类方法里我们 通过类LinearVtln实现了一种方法,程序有gmm-init-lvtln,gmm-train-lvtln-special, 和gmm-est-lvtln-trans。LinearVtln 对象主要存储线性特征变换的集合,每个扭曲因子一个变换。这些线性变换矩阵表示为


其中例如=31, 对应31个不同的扭曲因子. 下面将描述我们是怎么获得这些矩阵的。指定说话人的变换是通过下列方法估计的。首先,我们需要某种模型以及其对应的对齐,在示例脚本中,我们通常用单音子模型或者全三音子模型。利用这个模型及其对齐,以及原始的弯转特征,我们计算出估计传统CMLLR的统计信息。计算VTLN变换时,我们利用矩阵,计算偏移向量,来为变换最大化CMLLR辅助函数。最大化辅助函数值(i.e. 在不同 i上)的就成为该说话人的变换. 由于我们在估计一个均值偏移,我们基本上是通过将一种基于模型的谱均值归一化(或者一种只有偏移的CMLLR形式)和以线性变换来实现的VTLN结合起来,这样就避免需要去单独实现均值归一化。


参考文献:https://blog.csdn.net/shichaog/article/details/78441304

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值