Kaldi脚本运行的说明:http://kaldi-asr.org/doc/tutorial_running.html
有一个例子是从RM数据集创建训练集和测试集(/export/corpora5/LDC/LDC93S3A/rm_comp是数据集路径)
local/rm_data_prep.sh需要自己编写,运行命令
local/rm_data_prep.sh /export/corpora5/LDC/LDC93S3A/rm_comp
脚本运行前当前目录结构(data是新生成的目录)
• local : 包含当前数据的目录
• train : 数据库中分离出来的训练数据.
• test_* : 数据库中分离出来的测试数据.
观察example中这些目录,假设当前所在目录是data目录
执行命令行:
cd local/dict
head lexicon.txt
head nonsilence_phones.txt
head silence_phones.txt
从这能够看到生成的一些准备数据信息
有些文件不是通过Kaldi得到的,而是通过OpenFst自己准备得到,比如
•lexicon.txt : 词典文件
•silence.txt : 包含哪个音素是静音的,哪个音素不是静音的信息
到/train目录,查看生成文件信息(如果要用自己的数据集,以下文件必须自己手动处理生成)
head text
head spk2gender.map #这个文件是说话人性别映射文件,很多情况用不上
head spk2utt
head utt2spk
head wav.scp
对上述文件的解释如下:
• text – 此文件包含了语音和Kaldi要用的语音id的映射关系。这个文件会被转成整型格式-文本文件,但是文件名这些会被整型的id代替。
• spk2gender.map – 文件包含说话人和他们的性别信息映射。这在训练的时候也表现了说话人的唯一性。
• spk2utt – 这个是说话人标识和说话人对应所有语音标识的映射。
• utt2spk – 这个是语音id和语音所属说话人的一对一的映射关系。
• wav.scp – 特征提取的时候Kaldi会读取这个文件。它被解析成键值对,key是每行的第一个字符串(在例子里是语音的id,Integer型),值就是扩展的文件名。扩展文件名分为rxfilename(用于读)和wxfilename(用于写)。这两种文件名有各自的规则。
比如rxfilename:
"-"或者""意味着标准输入;
"some command |"意味着输入管道命令;
"/some/filename:12345"意味着文件的偏移量;
"/some/filename"…类似这样,任何不匹配前3条命令的都被当做普通文件来处理。
wxfilename也和rxfilename类似,除了没有文件偏移量。
具体可看http://www.kaldi-asr.org/doc/io.html#io_sec_xfilename。
验证训练集比测试集大很多的命令:
wc train/text test_feb89/text
下面的步骤就是用Kaldi去创建raw类型的语言模型
回到s5目录,执行以下命令:
utils/prepare_lang.sh data/local/dict '!SIL' data/local/lang data/lang
这就会创建lang文件夹,这里面包含了FST的描述所涉及的语言。prepare_lang.sh这个脚本把/data里面创建的一些文件转换成可以被Kaldi阅读的更规范的形式。这个脚本创建的输出文件在data/lang中。
接下来说的就是这里面的一些文件。
首先创建的两个文件就是words.txt和phones.txt,这是OpenFst格式符号表,是字符串到integer类型的映射关系。
有限状态机分为:接收器/识别器(输出一个二元状态,是或者不是);变换器(使用动作基于给定的输入和/或状态生成输出)分为Moore和Mealy FSM。两种变换器定义:Moore状态机的输出只与当前的状态有关,即:输出=f(当前状态);Mealy状态机的输出与当前状态和输入有关,即:输出=f(当前状态,输入)。不管是Moore机还是Mealy机,两者的下一状态都与当前状态和输入有关,即:下一状态=f(当前状态,输入),这是两种状态机模型的共性。
回到kaldi的fst格式,首先创建一个状态转移矩阵
# arc format: src dest ilabel olabel [weight]
# final state format: state [weight]
# lines may occur in any order except initial state must be first line
# unspecified weights default to 0.0 (for the library-default Weight type)
cat >text.fst <
创建输入和输出的结点以及对应的id
cat >isyms.txt < 0
a 1
b 2
c 3
EOF
cat >osyms.txt < 0
x 1
y 2
z 3
EOF
加入当前路径到环境变量
export PATH=.:$PATH
编译并创建二进制的FST
fstcompile --isymbols=isyms.txt --osymbols=osyms.txt text.fst binary.fst
把binary.fst的转移矩阵复制一份,权重乘以2倍生成到binary2.fst中
fstinvert binary.fst | fstcompose - binary.fst > binary2.fst
以文本形式查看fst转移矩阵
fstprint --isymbols=isyms.txt --osymbols=osyms.txt binary.fst
fstprint --isymbols=isyms.txt --osymbols=osyms.txt binary2.fst
删除生成的.fst和.txt信息
rm *.fst *.txt
看Kaldi的例子中
查看fst中的信息
fstprint --isymbols=data/lang/phones.txt --osymbols=data/lang/words.txt data/lang/L.fst | head
如果bash找不到fstprint命令,就要把OpenFST的安装路径加到系统环境变量中,简单跑下path.sh就行
. ./path.sh
下一步就是使用之前步骤创建的一些文件来生成FST描述的语言语法,回到s5目录执行
local/rm_prepare_grammar.sh
如果成功了,G.fst会在/data/lang中创建(PS:G代表Grammer,在识别中运用
特征提取
mfcc特征提取
找到run.sh中与mfcc相关的三行(决定特征存放目录,编辑对应例子),比如把特征存放到/my/disk/rm_mfccdir,就执行对应的脚本
export featdir=/my/disk/rm_mfccdir #存放位置
# make sure featdir exists and is somewhere you can write.
# can be local if you want.
mkdir $featdir #创建目录
for x in test_mar87 test_oct87 test_feb89 test_oct89 test_feb91 test_sep92 train; do \
steps/make_mfcc.sh --nj 8 --cmd "run.pl" data/$x exp/make_mfcc/$x $featdir; \ #生成mfcc
steps/compute_cmvn_stats.sh data/$x exp/make_mfcc/$x $featdir; \ #计算cmvn状态,做均值和方差规整
done
PS:run.pl会在命令运行时生成一些log信息之类的,可运用到每条命令中
多个CPU并行跑,可以更改—nj选项决定多少个任务一起跑。
可以查看log文件exp/make_mfcc/train/make_mfcc.1.log看创建MFCCs的输出。
查看make_mfcc.sh中split_scp.pl作用(生成raw_mfcc_train.1.scp)
执行命令
wc $featdir/raw_mfcc_train.1.scp
wc data/train/wav.scp
yesno数据集会得到如下效果:
[xx@localhost mfcc]$ wc raw_mfcc_train_yesno.1.scp
31 62 2897 raw_mfcc_train_yesno.1.scp
[xx@localhost mfcc]$ wc ../data/train_yesno/wav.scp
31 62 1488 ../data/train_yesno/wav.scp
Wc命令作用:统计字节数、字数、行数
如上则表示 31行 62个字符数(空格隔开的) 1488字节
wav.scp 表示 语音id 和 对应的 实际语音信息(存放目录)
raw_mfcc_train_yesno.1.scp 存放的是语音id 和 对应的总特征文件.ark中语音对应的字节偏移
查看调用计算mfcc特征(compute-mfcc-feats)脚本
调用这个工具时有个配置文件的机制,kaldi可以用这个文件来配置选项,就像HTK的配置文件一样,但是很少用。目标变量(scp和ark,scp这种需要更多解释)
在解释前,执行以下命令
head data/train/wav.scp
head $featdir/raw_mfcc_train.1.scp
less $featdir/raw_mfcc_train.1.ark
查看这些文件结构,ark文件是二进制文件
以文本形式看.ark文件的头几行
copy-feats ark:$featdir/raw_mfcc_train.1.ark ark,t:- | head
也可以去掉t:-(这代表text格式查看) 但是把管道置换为less更好,因为ark是二进制文件
还可以通过以下命令达到同样的效果
copy-feats scp:$featdir/raw_mfcc_train.1.scp ark,t:- | head
这是因为archive和script文件代表同样的东西(archive是script的1/8,分成了8份,注意ark:和scp:命令)。Kaldi不会从数据本身中找出什么是脚本文件或ark文件,实际上,Kaldi从不尝试从后缀区分文件。这是出于一般的哲学原因,也为了防止与管道的不良交互(因为管道通常没有名称)。
执行以下命令:
head -10 $featdir/raw_mfcc_train.1.scp | tail -1 | copy-feats scp:- ark,t:- | head
这条命令从1/10训练文件打印出一些数据。”scp:-“中的”-“表示标准输入读取,scp表示输入是脚本文件。
看script和archive文件到底是什么,第一点要做的就是用同样的方式看他们的代码。
举个简单的用户级别调用代码的例子,输入以下命令:
tail -30 ../../../src/featbin/copy-feats.cc
1
这里核心代码就三行。代码中迭代的方式和OpenFst中的状态迭代一样的(尝试和OpenFst中的尽可能样式一致)
其实script和archive文件就是一个“表”的概念。这个表就是一个排序好的条目(比如特征文件)集合,通过唯一的字符串(比如语音id)进行索引。“表”不是真的C++对象,因为C++对象访问数据是依靠是否写、迭代或者随机访问。这些对象类型的一个例子就是浮点矩阵(Matrix
BaseFloatMatrixWriter
RandomAccessBaseFloatMatrixReader
SequentialBaseFloatMatrixReader
这些实际上是模板类。
.scp和.ark文件都可以看成是数据表。这种格式有如下特点:
.scp格式是纯文本格式,一行有key的id和“可扩展文件名”让Kaldi去找数据
.ark格式可能是文本/二进制,”t”参数表示文本,默认是二进制。格式:key的id,空格,目标数据。
.scp和.ark文件几个通用的点:
指定读表的字符串叫rspecifier;比如 "ark:gunzip -c my/dir/foo.ark.gz|".
• 指定写表的字符串叫 wspecifier;比如 "ark,t:foo.ark".
• .ark文件可以共同连接起来,仍然是有效的ark文件(没有中心索引)
• 代码可以顺序或随机访问.scp和.ark文件。用户级代码只需要知道它是迭代还是查找,不需要知道访问的是哪种类型文件。
• Kaldi不会在.ark文件中表示对象类型;需要提前知道对象类型。
• .ark和.scp文件不包含混合类型
• 通过随机访问来读取.ark文件可能是无效的,因为代码可能必须将对象缓存在内存中。
• 为了有效地随机访问.ark文件,您可以使用“ark,scp”写入机制(例如用于将mfcc功能写入磁盘)来写出相应的脚本文件。 然后,通过scp文件访问它。
• 在档案上进行随机访问时,避免代码必须缓存一堆内容的另一种方法是告知代码归档归档并按排序顺序调用(例如“ark,s,cs: - ”))
.scp和.ark怎么在管道内使用的命令
head -1 $featdir/raw_mfcc_train.1.scp | copy-feats scp:- ark:- | copy-feats ark:- ark,t:- | head
注意输出要用head,否则会出现一大堆内容(.ark可能是二进制文件)
最后,为了方便起见,将所有测试数据合并到一个目录中。对这个一般的步骤进行所有的测试。 以下命令还将合并说话人,注意重复和重新生成这些说话人的统计信息,以便我们的工具不会出错。 通过运行以下命令(从s5目录)执行此操作。
utils/combine_data.sh data/test data/test_{mar87,oct87,feb89,oct89,feb91,sep92}
steps/compute_cmvn_stats.sh data/test exp/make_mfcc/test $featdir
创建训练数据的子集(train.1k),每个说话人只有1000条语音。
用此数据进行训练,命令:
utils/subset_data_dir.sh data/train 1000 data/train.1k
单音子训练
训练单音子模型。如果安装Kaldi的磁盘不大,使用exp/作为到大容量磁盘的软链接(如果跑实验不清理数据,很容易到几个G),输入:
nohup steps/train_mono.sh --nj 4 --cmd "$train_cmd" data/train.1k data/lang exp/mono &
查看最近的输出命令:
tail nohup.out
可以用–nj这种方式跑更长时间的任务,这样在断开连接时候也能跑完数据。用”screen”方式会更好,它不会被kill掉。实际上这个脚本(train_mono.sh)的标准输出(命令行输出)和错误(ERROR)很少输出,大部分的输出都是输出到exp/mono/下的log文件。
运行的时候看文件data/lang/topo。文件是实时生成的。每一个音素都有从其它音素来的不同拓扑结构。看data/phones.txt从数字id可以看出它到底是哪个音素。拓扑文件中的约定是第一个状态是初始状态(概率为1),最后一个状态是最终状态(概率为1)。
查看模型文件命令:
gmm-copy --binary=false exp/mono/0.mdl - | less
可以看到拓扑文件头部信息,在模型参数之前,还有一些其它东西。.mdl文件包含两个对象:转移概率模型包括HMM拓扑信息,还有一种相关模型类的对象(AmGmm)。 通过“包含两个对象”,意思是对象具有标准形式的写入和读取功能,调用这些函数将对象写入文件。 对于这样的对象,这不是表的一部分(即没有涉及到“ark:”或“scp:”),写入二进制或文本模式,可以由标准的命令行选项设置 -binary = true或-binary = false(不同的程序具有不同的默认值)。 对于表(.ark和.scp),是二进制还是文本模型由说明符中的”,t”选项控制。
上面命令是看模型包含哪种信息。要更多细节和模型的表示意义,则看HMM topology and transition modeling
有一个很重要的点,Kaldi中的pdf是用数字id表示的,从0开始。pdf在HTK中是没有名字的。而且.mdl模型文件是没有足够的信息来对上下文相关音素和pdf-id进行映射的。要看这些信息,需要查看树文件,执行命令:
copy-tree --binary=false exp/mono/tree - | less
格式示例:
ContextDependency 3 1 ToPdf TE 1 49 ( NULL SE -1 [ 0 1 ]
{ SE -1 [ 0 ]
{ CE 0 CE 54 }
CE 48 }
SE -1 [ 0 1 ]
{ SE -1 [ 0 ]
{ SE 0 [ 1 ]
{ SE 2 [ 4 19 36 ]
{ CE 1 CE 1612 }
SE 2 [ 4 19 36 ]
{ SE 0 [ 16 29 45 ]
{ CE 110 SE 0 [ 21 ]
{ CE 748 SE 0 [ 9 12 13 22 ]
{ SE 0 [ 9 ]
{ CE 623 CE 1599 }
这是单音素的“树”,单音素的“树”很小——它没有任何分裂。尽管这种树格式并不是很人性化,还是解释下:ToPdf后面,包含了多类型的EventMap对象,有个key,value的键值对,代表 上下文和HMM状态 ,映射到一个整型的pdf ID。来自EventMap的是类型ConstantEventMap(表示树的叶子),TableEventMap(表示某种查找表)和SplitEventMap(表示树分割)。在文件exp / mono / tree中,“CE”是ConstantEventMap的标记(对应于树的叶子),“TE”是TableEventMap的标记(没有“SE”或SplitEventMap,因为这个是单声道的情况)。 “TE 0 49”是TableEventMap的开始,它在key 0 上“分割”(表示在单声道情况下长度为1的音素上下文向量中的第0个音素位置)。在括号中,接下里是EventMap类型的49个对象。第一个为NULL,表示指向EventMap的零指针,因为将phone-id中的0留给“epsilon”。例子中非NULL对象是字符串“TE -1 3(CE 33 CE 34 CE 35)”,它表示在键-1上分割的TableEventMap。该键表示拓扑文件中指定的PdfClass,在本例中与HMM状态索引相同。该音素具有3个HMM状态,因此分配给该键的值可以取值0,1或2.括号内有三个类型为ConstantEventMap的对象,每个对象都表示树的叶子。
再来看看exp/mono/ali.1.gz文件(对齐文件)
copy-int-vector "ark:gunzip -c exp/mono/ali.1.gz|" ark,t:- | head -n 2
1
timit数据集的ali.1.gz文件格式示例如下:
faem0_si1392 2 4 3 3 3 3 3 3 6 5 5 5 5 38 37 37 37 40 42 218 217 217 217 217 217 217 217 217 217 217 217 220 219 222 221 221 248 247 247 247 247 247 247 250 252 176
……
这是训练数据的viterbi对齐文件;每个训练文件都有对应的一行。再对比看上面说的tree文件,找到数字最大的pdf-id(tree文件最后一个数字,比如timit中最大的数字是(48音素*3-1 = 143)),会发现对齐文件中的数字比最大的pdf-id还要大得多。为什么?因为对齐文件中的数字不包含pdf-id,而是包含一个稍微更细粒度的标识符,我们称之为“transition-id”。这也对音素的原型拓扑中的音素和状态转移对应地进行编码。
可以通过”show-transitions”命令来看转移-id的信息。
如果有占用计算文件?.occs文件,可以把这个文件当成第二个参数,这样会有更多信息
show-transitions data/lang/phones.txt exp/mono/0.mdl
1
得到结果如下:
Transition-state 1: phone = sil hmm-state = 0 pdf = 0
Transition-id = 1 p = 0.84957 [self-loop]
Transition-id = 2 p = 0.15043 [0 -> 1]
Transition-state 2: phone = sil hmm-state = 1 pdf = 1
Transition-id = 3 p = 0.87305 [self-loop]
Transition-id = 4 p = 0.12695 [1 -> 2]
Transition-state 3: phone = sil hmm-state = 2 pdf = 2
Transition-id = 5 p = 0.690806 [self-loop]
Transition-id = 6 p = 0.309194 [2 -> 3]
可以看到转移-id和音素及其状态的对应关系。
查看更人性化的对齐文件形式
show-alignments data/lang/phones.txt exp/mono/0.mdl "ark:gunzip -c exp/mono/ali.1.gz |" | less
会把同一个音素的状态用[ ]圈起来
更多信息像 HMM拓扑,转移-id,转移模型等东西,可以看 HMM topology and transition modeling.
查看训练的处理过程,输入命令:
grep Overall exp/mono/log/acc.{?,??}.{?,??}.log
可以看到每次迭代的声学似然。
还可以查看update.*.log文件看更新log的信息,命令如下:
grep Overall exp/mono/log/update.*.log
效果如下:
log/update.0.log:LOG (gmm-est[5.2]:main():gmm-est.cc:102) Transition model update: Overall 0.0380546 log-like improvement per frame over 1.12482e+06 frames.
log/update.0.log:LOG (gmm-est[5.2]:main():gmm-est.cc:113) GMM update: Overall 0.7529 objective function improvement per frame over 1.12482e+06 frames
log/update.0.log:LOG (gmm-est[5.2]:main():gmm-est.cc:116) GMM update: Overall avg like per frame = -111.162 over 1.12
当单音素训练完成后,可以测试单音素的解码过程。
在解码前,需要创建词图。输入命令:
utils/mkgraph.sh --mono data/lang exp/mono exp/mono/graph
查看utils / mkgraph.sh调用的程序。 这些程序的名字很多都以“fst”(例如fsttablecompose)开头,其中大多数程序实际上并不是来自OpenFst发行版。 Kaldi创建了一些自己的FST操作程序。 可以通过后面的命令找到这些程序的位置。 使用在utils / mkgraph.sh中调用的任意程序(例如fstdeterminizestar)。 然后输入:
which fstdeterminizestar
可以找到程序位置。
有不同版本的程序的原因主要是因为在语音识别中使用FST有一点差别(较少的AT&T-ish)。 例如,“fstdeterminizestar”对应于删除ε弧的“classical”。 有关更多信息,查看Decoding graph construction in Kaldi。
在图创建过程之后,我们可以通过以下方式开始单音子解码:
steps/decode.sh --config conf/decode.config --nj 20 --cmd "$decode_cmd" \
exp/mono/graph data/test exp/mono/decode
可以看看一些解码的输出文件
less exp/mono/decode/log/decode.2.log
可看到屏幕上输出了转录的文件(生成的标注)。转录文件的文本形式只在日志信息中出现:程序实际上的输出在exp/mono/decode/scoring/2.tra中。这些tra文件代表了使用解码过程的语言模型(LM)。LM的范围默认使用2-13。
查看实际的解码序列,输入命令:
utils/int2sym.pl -f 2- data/lang/words.txt exp/mono/decode/scoring/2.tra
还有个脚本叫sym2int,可以转回来:
utils/int2sym.pl -f 2- data/lang/words.txt exp/mono/decode/scoring/2.tra | \
utils/sym2int.pl -f 2- data/lang/words.txt
The -f 2- 选项的意思是避免把utt-id转换成int类型。输入命令:
tail exp/mono/decode/log/decode.2.log
会打印末尾一些有用的总结信息,包括实时因子和每帧的平均对数似然。实时因子一般是0.2到0.3之间(比实时更快)。取决于CPU和用了多少线程执行任务和一些其它因素。脚本并行运行20个作业,如果机器少于20个内核就会慢得多。注意解码过程中用了很宽容的剪枝(值为20),以获取更精确的结果。在典型的大词汇量的语音识别集中,beam会小得多(大概13)。
再次查看日志文件的顶部,并专注于命令行。 可选参数位于目标参数之前(这是必需的)。输入
gmm-decode-faster
查看使用信息,并将参数和log文件看到的参数匹配。回想 “rspecifier” 是专门用来读表的字符串之一, “wspecifier”是专门用来写入表的一个。
单音子系统介绍结束,后续可看三音子系统。
Up: Kaldi tutorial
Previous: Overview of the distribution
Next: Reading and modifying the code
http://www.kaldi-asr.org/doc/tutorial_code.html
阅读和修改代码(1/2小时)
Up:Kaldi教程
上一页:运行示例脚本
当triphone系统构建正在运行时,我们将花一些时间来浏览代码的某些部分。您将从本教程的这一部分中获得的主要内容是如何组织代码以及依赖结构是什么的一些想法; 以及修改和调试代码的一些经验。如果您想更深入地理解代码,我们建议您按照主文档页面上的链接进行操作,我们将按主题组织更详细的文档。
转到顶级目录(我们称之为kaldi-1)然后转到src /。首先看一下文件库/ kaldi-common.h(不要按照本文档中的链接;从shell或编辑器中查看)。#包含了几乎所有Kaldi程序使用的base /目录中的一些内容。您可以从文件名中猜测提供的内容类型:错误记录宏,typedef,数学效用函数(如随机数生成)和杂项#defines。但这是一套精简的公用事业; 在util / common-utils.h中有一个更完整的集合,包括命令行解析和处理扩展文件名(如管道)的I / O函数。花几秒钟浏览一下util / common-utils.h并看看#includes。我们将一个实用程序子集隔离到base /目录中的原因是我们可以最小化matrix /目录的依赖性(这本身很有用); matrix /目录仅取决于base /目录。查看matrix / Makefile并搜索base /以查看如何指定。在Makefile中查看此类规则可以让您深入了解工具包的结构。
现在看一下文件matrix / matrix-lib.h。查看它包含的文件。这提供了矩阵库中各种事物的概述。这个库基本上是BLAS和LAPACK的C ++包装器,如果这对您来说意味着什么(如果没有,请不要担心)。文件sp-matrix.h和tp-matrix.h分别涉及对称打包矩阵和三角打包矩阵。快速扫描文件矩阵/ kaldi-matrix.h。这将让您了解矩阵代码的外观。它由表示矩阵的C ++类组成。我们在这里提供矩阵库的迷你教程, 如果你感兴趣。您可能会注意到代码中看起来像一个奇怪的注释样式,注释以三个斜杠(///)开头。这些类型的表扬和阻止评论开头
,由Doxygen软件解释,自动生成文档。它还会生成您正在阅读的页面(此类文档的源代码位于src / doc /中)。
将此代码添加到文件matrix-lib-test.cc中,就在函数MatrixUnitTest()的上方。然后,在MatrixUnitTest()内添加以下行:
在您添加此功能的位置无关紧要。然后输入“make test”。应该有一个错误(分号应该是逗号); 解决它,然后再试一次。现在输入“./matrix-lib-test”。这应该会因断言失败而崩溃,因为单元测试代码中还有另一个错误。接下来我们将调试它。类型
(如果你在cygwin上,你现在应该输入gdb提示符,“break __assert_func”)。输入“r”。当它崩溃时,它会调用abort(),它会被调试器捕获。键入“bt”以查看堆栈跟踪。通过键入“up”来填充堆栈,直到您进入测试功能。当你在正确的地方,你应该看到如下输出:
#5 0x080943cf in kaldi::UnitTestAddVec () at matrix-lib-test.cc:2568
2568 AssertEqual(a, b); // will crash if not equal to within 如果不等于内部将崩溃
如果你走得太远,你可以输入“向下”。然后键入“p a”和“p b”以查看a和b的值(“p”是“print”的缩写)。你的屏幕应该看起来像这样:
(gdb) p a
$5 = -0.931363404
(gdb) p b
$6 = -0.270584524
(gdb)
当然,确切的值是随机的,可能对您有所不同。由于数字差别很大,很明显,这不仅仅是容差错误的问题。通常,您可以使用“print”表达式从调试器访问任何类型的表达式,但括号运算符(像“v(i)”这样的表达式)不起作用,因此要查看您必须输入的向量内的值表达式如下:
(gdb) p v.data_[0]
$8 = 0.281656802
(gdb) p w.data_[0]
$9 = -0.931363404
(gdb) p w2.data_[0]
$10 = -1.07592916
(gdb)
这可能会帮助您确定“b”的表达式是错误的。将其修复到代码中,重新编译并再次运行(您只需在gdb提示符中键入“r”即可重新运行)。它现在应该运行正常。强制gdb在以前失败的位置进入代码,这样你就可以再次检查表达式的值,看看现在的工作正常。要使调试器中断,您必须设置断点。计算出断言失败的行号(在UnitTestAddVec()中的某个地方),然后键入gdb,如下所示:
(gdb) b matrix-lib-test.cc:2568
Breakpoint 1 at 0x80943b4: file matrix-lib-test.cc, line 2568. (4 locations)
//断点1位于0x80943b4:文件matrix-lib-test.cc,第2568行。(4个位置)
然后运行程序(键入“r”),当它在那里中断时,使用“p”命令查看表达式的值。要继续,请键入“c”。它会一直停在那里,因为它在一个循环中。键入“d 1”以删除断点(假设它是第一个断点),并键入“c”继续。该程序应该运行到最后。输入“q”退出调试器。如果需要调试带有命令行参数的程序,可以这样做:
gdb --args kaldi-program arg1 arg2 ...
(gdb) r
...
或者你可以不带参数调用gdb,然后在提示符下键入“r arg1 arg2 ...”。
当你完成并编译完成后,输入
git diff
看看你做了哪些改变。如果您正在为Kaldi项目做贡献并计划在不久的将来向我们发送代码,您可能希望将它们提交到分支中,如下所述,以便您可以在以后生成干净的GitHub拉取请求。我们建议您熟悉Git分支,即使您没有直接贡献您的更改; Git是一个强大的工具,可以维护您的本地代码更改以及您可能贡献的更改。
接下来看gmm / diag-gmm.h(这个类存储高斯混合模型)。DiagGmm类可能看起来有点令人困惑,因为它有许多不同的访问器功能。搜索“私有”并查看类成员变量(它们总是以下划线结尾,按照Kaldi风格)。这应该清楚我们如何存储GMM。这只是一个GMM,而不是GMM的整个集合。看看gmm / am-diag-gmm.h; 这个类存储了一组GMM。请注意,它不会从任何东西继承。搜索“私有”,您可以看到成员变量(只有两个)。你可以从中理解这个类是多么简单(其他一切都包含各种访问器和便利功能)。一个自然要问的问题是:转换在哪里,决策树在哪里,HMM拓扑在哪里?所有这些都与声学模型分开,因为研究人员可能希望在保持系统其余部分相同的同时替换声学可能性。我们稍后会谈到其他的东西。
接下来看一下feat / feature-mfcc.h。专注于MfccOptions结构。结构成员可以让您了解MFCC特征提取支持哪种选项。请注意,某些struct成员本身就是选项结构。查看Register函数。这是Kaldi选项课程的标准。然后查看featbin / compute-mfcc-feats.cc(这是一个命令行程序)并搜索Register。您可以看到options结构的Register函数的调用位置。要查看MFCC特征提取支持的选项的完整列表,请执行不带参数的程序featbin / compute-mfcc-feats。回想一下,您看到其中一些选项在MfccOptions类中注册,其他选项在featbin / compute-mfcc-feats.cc中注册。指定选项的方法是-option = value。类型
featbin/compute-mfcc-feats ark:/dev/null ark:/dev/null
这应该成功运行,因为它将/ dev / null解释为空存档。您可以尝试使用此示例设置选项。试试看,例如,
featbin / compute-mfcc-feats --raw-energy = false ark:/ dev / null ark:/ dev / null
您从中获得的唯一有用信息是它不会崩溃; 尝试删除“=”符号或缩写选项名称或更改参数数量,并查看它失败并打印用法消息。
接下来看一下tree / build-tree.h。找到BuildTree函数。这是构建决策树的主要顶级功能。请注意,它返回一个EventMap类型的指针。这是一种将函数从一组(键,值)对存储到整数的类型。它在tree / event-map.h中定义。键和值都是整数,但键表示语音上下文位置(通常为0,1或2),值表示电话。还有一个特殊键-1,大致代表HMM中的位置。转到实验目录(../egs/rm/s5),我们将看看如何构建树。BuildTree函数的主要输入是BuildTreeStatsType类型,它是一个typedef,如下所示:
typedef vector > BuildTreeStatsType;
这里,EvenType是以下typedef:
typedef vector > EventType;
EventType表示一组(键,值)对,例如典型的一对{{-1,1},{0,15},{1,21},{2,38}},代表电话21电话15的左上下文,电话38的右上下文和“pdf-class”1(在正常情况下意味着它处于状态编号1,其为三个状态的中间)。Clusterable *指针是一个指向虚拟类的指针,该虚拟类具有通用接口,支持将统计数据添加到一起并评估某种目标函数(例如可能性)等操作。在正常的配方中,它实际上指向一个包含足够统计数据来估计对角高斯pdf的类。
做
less exp/tri1/log/acc_tree.log
此文件中没有太多信息,但您可以看到命令行。该程序为每个看到的三音素上下文的每个HMM状态(实际上是pdf级)累积单高斯统计。这些–ci-phones
选项使得它知道避免为不同的手机上下文累积单独的统计信息,例如我们不希望依赖于上下文的沉默(这是一种优化;没有这个选项就可以工作)。这个程序的输出可以被认为是上面讨论的BuildTreeStatsType类型,虽然为了阅读它,我们必须知道它是什么具体类型。
做
less exp / tri1 / log / train_tree.log
该程序执行决策树聚类; 它读取输出的统计数据。它基本上是上面讨论的BuildTree函数的包装器。它在决策树聚类中提出的问题会自动生成,如脚本steps / train_tri1.sh中所示(查找程序cluster-phones和compile-questions)。
接下来看看hmm / hmm-topology.h。HmmTopology类为许多电话定义了一组HMM拓扑。通常,每部电话可以具有不同的拓扑。拓扑包括用于初始化的“默认”转换。查看标题顶部的扩展注释中的示例拓扑。有一个标签