OfficialKaldi(五)| 示例脚本(翻译注解)
入门和前提条件。
本教程的下一阶段是开始运行资源管理示例脚本。将目录更改为顶层(我们将其称为kaldi-1),然后更改为egs /。查看该目录中的README.txt文件,尤其是查看“资源管理”部分。它提到了与语料库相对应的LDC目录号。这可以帮助您从LDC获取数据。如果由于某种原因而无法获取数据,则只需继续阅读本教程并执行没有数据就可以执行的步骤,您仍然可以从中获得一些价值。最好的情况是,系统上有一些目录,例如/ export / corpora5 / LDC / LDC93S3A / rm_comp,其中包含三个子目录。分别命名为rm1_audio1,rm1_audio2和rm2_audio。这些将与来自LDC的数据分发中的三个原始磁盘相对应。这些说明假定您的shell是bash。如果您使用的是其他Shell,则这些命令将不起作用或应进行修改(只需键入“ bash”即可进入bash,并且一切正常。)
现在将目录更改为rm /,浏览文件README.txt以查看整体结构,将cd更改为s5 /。这是实验的基本顺序,与该工具包第5版中的主要功能相对应。
在s5 /中,列出目录并浏览RESULTS文件,以便您对其中的内容有所了解(以后,应验证所获得的结果与其中的结果相似)。我们将要查看的主要文件是run.sh。注意:run.sh不能直接从shell运行;这个想法是您手动逐个运行其中的命令。
资料准备
我们首先需要配置作业是需要在本地运行还是在Oracle GridEngine上运行。有关如何执行此操作的说明在cmd.sh中。
如果未安装GridEngine,或者正在较小的数据集上运行实验,请在Shell上执行以下命令。
train_cmd =“ run.pl” encode_cmd =“ run.pl”
如果确实安装了GridEngine,则应将queue.pl文件与指定GridEngine驻留位置的参数一起使用。在这种情况下,您将执行以下命令(参数-q是示例,您希望将其替换为GridEngine详细信息)
train_cmd =“ queue.pl -q all.q@a*.clsp.jhu.edu” encode_cmd =“ queue.pl -q all.q @ [ah] *。clsp.jhu.edu”
下一步是从RM语料库创建测试和训练集。为此,请在外壳程序上运行以下命令(假设您的数据位于/ export / corpora5 / LDC / LDC93S3A / rm_comp中):
本地/rm_data_prep.sh / export / corpora5 / LDC / LDC93S3A / rm_comp
如果这可行,则应该说:“ RM_data_prep成功”。如果没有,您将必须找出脚本失败的原因以及问题所在。
现在列出当前目录的内容,您应该看到已经创建了一个名为“ data”的新目录。进入新创建的数据目录并列出内容。您应该看到三种主要的文件夹类型:
- local:包含当前数据的字典。
- 训练:从语料库中分割出来的用于训练的数据。
- test_ *:从语料库中分割出来的数据,用于测试。
让我们花一些时间来查看创建的数据文件。这应该使您对Kaldi如何期望输入数据有一个很好的了解。(有关更多详细信息,请参阅:详细的数据准备指南)
本地目录:假设您位于数据目录中,请执行以下命令:
cd本地/字典 头lexicon.txt 头nonsilence_phones.txt 头silence_phones.txt
这些将使您大致了解通用数据准备过程的输出。您应该意识到的是,并非所有这些文件都是“本机” Kaldi格式,即,并非所有文件都可以被Kaldi的C ++程序读取,并且需要在使用KalF之前使用OpenFST工具进行处理。
- lexicon.txt:这是词典。
- * silence * .txt:这些文件包含有关哪些电话保持静音以及哪些电话保持静音的信息。
现在返回到数据目录,并将目录更改为/ train。然后执行以下命令以查看此目录中文件的输出:
标题文字 头spk2gender 头spk2utt 头utt2spk 头wav.scp
- 文本-此文件包含发声和发声ID之间的映射,这些将由Kaldi使用。该文件将转换为整数格式-仍然是文本文件,但单词将替换为整数。
- spk2gender-此文件包含说话者及其性别之间的映射。这也充当参与培训的唯一用户的列表。
- spk2utt-这是说话者标识符和与说话者相关的所有话语标识符之间的映射。
- utt2spk-这是发话ID与相应说话者标识符之间的一对一映射。
- wav.scp-进行特征提取时,Kaldi程序实际上直接读取此文件。再次查看文件。它被解析为一组键值对,其中键是每一行的第一个字符串。该值是一种“扩展文件名”,您可以猜测它是如何工作的。由于它是供阅读的,因此我们将这种类型的字符串称为“ rxfilename”(在编写时,我们使用术语wxfilename)。请参阅
- 扩展文件名:rxfilenames和wxfilenames(
- 如果您感到好奇)。请注意,尽管我们使用扩展名.scp,但从HTK的角度来看,它不是脚本文件(即,它不被视为命令行参数的扩展名)。
火车文件夹和test_ *文件夹的结构相同。但是,火车数据的大小明显大于测试数据。您可以通过返回数据目录并执行以下命令来验证这一点,该命令将给出训练和测试集的字数:
wc火车/文字test_feb89 /文字
下一步是创建Kaldi使用的原始语言文件。在大多数情况下,这些文件将是整数格式的文本文件。确保返回到s5目录并执行以下命令:
utils / prepare_lang.sh数据/本地/ dict'!SIL'数据/本地/ lang数据/ lang
这将在本地文件夹中创建一个名为lang的新文件夹,其中将包含描述所讨论语言的FST。看一下脚本。它将以data /创建的某些文件转换为Kaldi读取的更规范的形式。该脚本在data / lang /目录中创建其输出。我们下面提到的文件将在该目录中。
该脚本创建的前两个文件称为words.txt和phones.txt(都在目录data / lang /中)。这些是OpenFst格式的符号表,表示从字符串到整数再到整数的映射。查看这些文件;因为它们很重要并且会经常使用,所以您需要了解其中的内容。它们具有与我们在发行概述中遇到的符号表格式相同的格式。
查看后缀为.csl的文件(在data / lang / phones中)。这些是分别用非冒号和无声电话的整数ID冒号分隔的列表。有时需要它们作为程序命令行上的选项(例如,指定静音电话的列表),以及用于其他目的。
查看phone.txt(在data / lang /中)。该文件是电话符号表,还处理标准FST配方中使用的“消歧符号”。这些符号通常称为#1,#2等。参见论文“带加权有限状态传感器的语音识别”。我们还添加了一个符号#0来替换语言模型中的epsilon转换;有关更多信息,请参见消歧符号。有多少个消歧符号?在某些配方中,消歧符号的数量与共享相同发音的单词的最大数量相同。在我们的食谱中,还有更多。你可以在这里找到更多的解释。
文件L.fst是FST格式的编译词典。要查看其中包含哪种信息,可以(从s5 /)执行以下操作:
fstprint --isymbols = data / lang / phones.txt --osymbols = data / lang / words.txt data / lang / L.fst | 头
如果bash找不到命令fstprint,则需要将OpenFST的安装路径添加到PATH环境变量中。只需运行脚本path.sh即可:
。./path.sh
下一步是使用在上一步中创建的文件来创建描述该语言语法的FST。为此,请返回目录s5并执行以下命令:
本地/rm_prepare_grammar.sh
如果成功,则返回“成功为RM准备语法”消息。将在/ data / lang中创建一个名为G.fst的新文件。
特征提取
下一步是提取训练功能。在run.sh中搜索“ mfcc”并运行相应的三行脚本(您必须确定要首先放置功能的位置并相应地修改示例)。确保您决定放置功能的目录具有足够的空间。假设我们决定将功能放在/ my / disk / rm_mfccdir上,我们将执行以下操作:
导出featdir = / my / disk / rm_mfccdir #确保featdir存在并且在可以写的地方。 #如果需要,可以是本地的。 mkdir $ featdir 对于test_mar87 test_oct87 test_feb89 test_oct89 test_feb91 test_sep92火车中的x; 做\ 步骤/make_mfcc.sh --nj 8 --cmd“ run.pl”数据/ $ x exp / make_mfcc / $ x $ featdir; \ 步骤/compute_cmvn_stats.sh数据/ $ x exp / make_mfcc / $ x $ featdir; \ 完成
运行这些作业。它们并行使用多个CPU,应该在一台快速的计算机上在大约两分钟内完成。您可以根据计算机的CPU数量更改–nj选项(指定要运行的作业数量)。查看文件exp / make_mfcc / train / make_mfcc.1.log以查看创建MFCC的程序的日志输出。在它的顶部,您将看到命令行(除非您指定–print-args = false,否则Kaldi程序将始终回显命令行)。
在脚本步骤/make_mfcc.sh中,查看调用split_scp.pl的行。您可能会猜到这是做什么的。
通过键入
wc $ featdir / raw_mfcc_train.1.scp wc数据/火车/wav.scp
您可以确认。
接下来看一下调用compute-mfcc-feats的行。选项应该是不言自明的。涉及配置文件的选项是一种机制,可以在Kaldi中用来传递配置选项,例如HTK配置文件,但实际上很少使用。位置参数(以“ scp”和“ ark,scp”开头的参数需要更多说明。
在我们对此进行解释之前,请再次查看脚本中的命令行,并使用以下命令检查输入和输出:
头数据/火车/wav.scp 头$ featdir / raw_mfcc_train.1.scp 减少$ featdir / raw_mfcc_train.1.ark
请注意-.ark文件包含二进制数据(如果终端在查看后无法立即使用,则可能必须键入“ reset”)。
通过列出文件,您可以看到.ark文件很大(因为它们包含实际数据)。您可以通过键入以下内容来更方便地查看这些归档文件之一(假设您位于s5目录中并具有运行脚本path.sh):
复制功能区柜:$ featdir / raw_mfcc_train.1.ark区柜,t:-| 头
您可以从此命令中删除“,t”修饰符,然后根据需要尝试重试–但是将其传递到“ less”可能是一个好习惯,因为数据将是二进制的。查看相同数据的另一种方法是:
复制专案scp:$ featdir / raw_mfcc_train.1.scp ark,t:-| 头
这是因为这些归档文件和脚本文件都表示相同的数据(嗯,从技术上讲,归档文件只代表其中的八分之一,因为我们将其分为八部分)。注意这些命令中的“ scp:”和“ ark:”前缀。Kaldi不会尝试从数据本身确定某个文件是脚本文件还是存档格式,实际上,Kaldi从未尝试过从文件后缀中找出内容。这是出于一般的哲学原因,也是为了防止与管道的不良互动(因为管道通常没有名称)。
现在键入以下命令:
头-10 $ featdir / raw_mfcc_train.1.scp | 尾巴-1 | 复制专案scp:-ark,t:-| 头
这会从第十个训练文件中打印出一些数据。注意,在“ scp:-”中,“-”告诉它从标准输入中读取,而“ scp”告诉它将输入解释为脚本文件。
接下来,我们将描述实际的脚本和存档文件。我们要说明的第一点是,代码以相同的方式看到它们两者。对于用户级调用代码的一个特别简单的示例,请键入以下命令:
尾巴-30 ../../../src/featbin/copy-feats.cc
您可以看到该程序实际完成工作的部分只有三行代码(实际上有两个分支,每个分支都有三行代码)。如果您熟悉OpenFst中的StateIterator类型,您会注意到我们的迭代方式是相同的样式(我们尝试与OpenFst尽可能兼容样式)。
表的概念是基础脚本和归档文件。表格基本上是一组有序的项目(例如功能文件),由唯一的字符串(例如发话标识符)索引。Table并不是真正的C ++对象,因为我们有单独的C ++对象来访问数据,具体取决于我们是编写,迭代还是进行随机访问。这些类型的示例(其中所讨论的对象是浮点矩阵(Matrix ))是:
BaseFloatMatrixWriter RandomAccessBaseFloatMatrixReader SequentialBaseFloatMatrixReader
这些类型都是实际上是模板类的typedef。我们将不在这里进一步详细介绍。脚本(.scp)文件或存档(.ark)文件都被视为数据表。格式如下:
- .scp格式是纯文本格式,其中包含带有键的行,然后是“扩展文件名”,告诉Kaldi在哪里可以找到数据。
- 存档格式可以是文本或二进制(您可以使用“,t”修饰符以文本模式编写;默认为二进制)。格式为:键(例如发话ID),然后是空格,然后是对象数据。
有关脚本和档案的一些一般要点:
- 指定如何读取表(归档文件或脚本)的字符串称为rspecifier。例如“ ark:gunzip -c my / dir / foo.ark.gz |”。
- 指定如何编写表(归档文件或脚本)的字符串称为wspecifier;例如“ ark,t:foo.ark”。
- 档案可以串联在一起,仍然是有效的档案(其中没有“中央索引”)。
- 该代码可以依次或通过随机访问读取脚本和存档。用户级代码仅知道它是在迭代还是在进行查找。它不知道它是访问脚本还是存档。
- Kaldi不会尝试在归档文件中表示对象类型。您必须提前知道对象类型
- 归档文件和脚本文件不能包含类型的混合。
- 由于代码可能必须将对象缓存在内存中,因此通过随机访问读取档案可能会导致内存效率低下。
- 为了有效地随机访问档案,您可以使用“ ark,scp”写入机制(例如,用于将mfcc功能写入磁盘)来写入相应的脚本文件。然后,您将通过scp文件访问它。
- 避免对档案进行随机访问时代码必须在内存中缓存一堆东西的另一种方法是告诉代码档案已排序并且将按排序顺序调用(例如“ ark,s,cs:-”) 。
- 读写档案的类型以Holder类型为模板,Holder类型是“知道如何”读写相关对象的类型。
在这里,我们给出了一个非常快速的概述,可能会提出更多的问题而不是提供答案。它只是为了使您知道所涉及的问题种类。有关更多详细信息,请参见Kaldi I / O机制。
为了使您了解如何在管道中使用归档和脚本文件,请键入以下命令并尝试了解发生了什么:
头-1 $ featdir / raw_mfcc_train.1.scp | 复制专案scp:-方舟:-| 复制功能方舟:-方舟,t:-| 头
依次运行这些命令并观察会发生什么可能会有所帮助。使用copy-feats时,请记住将输出传递到head,因为您可能会列出很多内容(对于ark文件,可能是二进制内容)。
最后,为方便起见,让我们将所有测试数据合并到一个目录中。我们将在此平均步骤上进行所有测试。下列命令还将合并发言人,并注意为这些发言人复制和重新生成统计信息,以便我们的工具不会抱怨。通过运行以下命令(从s5目录)来执行此操作。
utils / combine_data.sh数据/测试数据/ test_ {mar87,oct87,feb89,oct89,feb91,sep92} 步骤/compute_cmvn_stats.sh数据/测试exp / make_mfcc /测试$ featdir
我们还创建一个训练数据的子集(train.1k),每个说话者仅保留1000言语。我们将使用训练。通过执行以下命令来执行此操作:
utils / subset_data_dir.sh数据/火车1000数据/火车.1k
单音训练
下一步是训练单声道模型。如果安装Kaldi的磁盘不大,则可能需要将exp /链接到大磁盘上某个目录的软链接(如果运行所有实验并且不清理,则最多可以启动几个磁盘)。 GB)。类型
nohup步骤/train_mono.sh --nj 4 --cmd“ $ train_cmd”数据/train.1k数据/ lang exp / mono&
您可以通过键入以下内容查看最新的输出
尾巴nohup.out
您可以用这种方式运行更长的作业,以便即使我们断开连接也可以完成运行,尽管更好的主意是从“屏幕”运行您的shell,以免被杀死。实际上只有很少的输出可以输出到标准输出和该脚本的错误。其中大部分用于记录exp / mono /中的日志文件。
运行时,查看文件data / lang / topo。该文件将立即创建。其中一部电话的拓扑结构与其他电话不同。查看data / phones.txt,以便从数字ID中找出它是哪部手机。请注意,拓扑文件中的每个条目都有一个最终状态,没有过渡。拓扑文件中的约定是,第一个状态是初始状态(概率为1),最后一个状态是最终状态(概率为1)。
类型
gmm-copy --binary = false exp / mono / 0.mdl-| 减
并查看模型文件。您将看到它包含在拓扑文件顶部的信息,然后在模型参数之前包含其他信息。约定是.mdl文件包含两个对象:一个类型为TransitionModel的对象,其中包含拓扑信息作为HmmTopology类型的成员变量,以及一个相关模型类型的对象(在这种情况下,类型为AmGmm)。“包含两个对象”是指对象具有标准形式的Write和Read函数,我们称这些函数将对象写到文件中。对于不属于表的此类对象(即不涉及“ ark:”或“ scp:”),写入采用二进制或文本模式,可以通过标准命令行选项–binary = true或–binary = false(不同的程序具有不同的默认值)进行控制。对于表(即归档文件和脚本),二进制或文本模型由说明符中的“,t”选项控制。
浏览模型文件以查看其包含的信息类型。在这一点上,我们将不更详细地介绍如何在Kaldi中表示模型。请参阅HMM拓扑和过渡建模以了解更多信息。
但是,我们将提到一个重要的点:Kaldi中的pdf是以数字id表示的,从零开始(我们称为这些pdf-id)。它们没有HTK中的“名称”。.mdl文件没有足够的信息来映射上下文相关的电话和pdf-id。有关该信息,请参见树文件:do
复制树--binary = false exp / mono / tree-| 减
请注意,这是单声道的“树”,因此非常琐碎–它没有任何“拆分”。尽管这种树格式并不是很容易让人理解,但是我们收到了许多有关树格式的查询,因此我们将对其进行解释。随便的读者可以跳过本段的其余部分。在“ ToPdf”之后,树文件包含多态类型EventMap的对象,可以将其视为存储从一组表示上下文中的电话和HMM状态的整数(键,值)对的映射。 pdf数字ID。从EventMap派生的类型有ConstantEventMap(代表树的叶子),TableEventMap(代表某种查找表)和SplitEventMap(代表树拆分)。在此文件exp / mono / tree中,“ CE” 是用于ConstantEventMap的标记(并与树的叶子相对应),而“ TE”是用于TableEventMap的标记(不存在“ SE”或SplitEventMap,因为这是单音的情况)。“ TE 0 49”是TableEventMap的开始,它在键0上“拆分”(对于单音手机情况,代表长度为1的电话上下文向量中的第零个电话位置)。括号后是EventMap类型的49个对象。第一个为NULL,表示指向EventMap的零指针,因为电话ID零保留用于“ epsilon”。非空对象的示例是字符串“ TE -1 3(CE 33 CE 34 CE 35)”,它表示在键-1上拆分的TableEventMap。此项表示在拓扑文件中指定的PdfClass,在我们的示例中与HMM状态索引相同。
现在看一下文件exp / mono / ali.1.gz(如果培训已经进行得足够多,它应该存在):
复制诠释向量“ ark:gunzip -c exp / mono / ali.1.gz |” ark,t:-| 头-n 2
这是训练数据的维特比对齐;每个训练文件只有一行。现在,再次查看exp / mono / tree(如上所述),并寻找编号最高的pdf id(这是文件中的最后一个数字)。将其与exp / mono / ali.1.gz中的数字进行比较。看起来有问题吗?路线中的数字太大。原因是对齐文件不包含pdf id。它包含一个更细粒度的标识符,我们称之为“ transition-id”。这还将对电话以及电话原型拓扑中的过渡进行编码。出于多种原因,这很有用。如果您想解释一个特定的transition-id是什么(例如,您正在cur.ali中查看一个比对,并且看到一个重复重复很多,并且想知道为什么),则可以使用以下程序:
show-transitions data / lang / phones.txt exp / mono / 0.mdl
如果您有一个包含占用计数的文件(名为* .occs的文件),则可以将其作为第二个参数,它将为您显示更多信息。
要以更人性化的方式查看路线,请尝试以下操作:
show-alignments data / lang / phones.txt exp / mono / 0.mdl“ ark:gunzip -c exp / mono / ali.1.gz |” | 减
有关HMM拓扑,过渡ID,过渡建模等内容的更多详细信息,请参见HMM拓扑和过渡建模。
接下来,让我们看看培训的进展情况(此步骤假定您的shell是bash)。类型
grep总exp / mono / log / acc。{?,??}。{?,??}。log
您可以在每次迭代中看到声学似然。接下来,查看文件exp / mono / log / update。*。log之一,以查看更新日志中包含哪些信息。
单音培训结束后,我们可以测试单音解码。在解码之前,我们必须创建解码图。类型:
utils / mkgraph.sh --mono数据/ lang exp / mono exp / mono / graph
查看utils / mkgraph.sh调用的程序。其中许多程序的名称都以“ fst”开头(例如fsttablecompose),其中大多数程序实际上并非来自OpenFst发行版。我们创建了一些自己的FST操作程序。您可以如下找到这些程序的位置。采取在utils / mkgraph.sh中调用的任意程序(例如fstdeterminizestar)。然后输入:
哪个明星
我们拥有不同版本程序的原因主要是因为我们在语音识别中使用FST的方式略有不同(AT&T-ish较少)。例如,“ fstdeterminizestar”对应于“经典”确定,其中删除了epsilon弧。有关更多信息,请参见Kaldi中的解码图构造。完成图创建过程后,我们可以通过以下方式开始单声道解码:
steps / decode.sh --config conf / decode.config --nj 20 --cmd“ $ decode_cmd” \ exp / mono /图形数据/测试exp / mono /解码
看一些解码输出
较少exp / mono / decode / log / decode.2.log
您会看到它会将成绩单显示在屏幕上。成绩单的文本形式仅出现在日志记录信息中:该程序的实际输出出现在文件exp / mono / decode / scoring / 2.tra中。这些tra文件中的数字表示解码过程中使用的语言模型(LM)比例。在这里,我们默认使用LM缩放比例,范围从2到13(有关详细信息,请参阅local / score.sh)。要查看tra文件中的实际解码单词序列(以2.tra为例),请键入:
utils / int2sym.pl -f 2-数据/lang/words.txt exp / mono / decode / scoring / 2.tra
有一个对应的脚本,名为sym2int.pl。您可以通过键入以下内容将其转换回整数形式:
utils / int2sym.pl -f 2- data / lang / words.txt exp / mono / decode / scoring / 2.tra | \ utils / sym2int.pl -f 2-数据/lang/words.txt
该-f 2-选项是为了避免尝试将语音ID转换为整数。接下来,尝试做
tail exp / mono / decode / log / decode.2.log
最后,它将打印出一些有用的摘要信息,包括实时系数和每帧的平均对数可能性。实时因子通常约为0.2到0.3(即比实时更快)。这取决于您的CPU,计算机上的作业数量以及其他因素。该脚本并行运行20个作业,因此,如果您的计算机的内核数少于20,则速度可能会慢得多。请注意,为了获得准确的结果,我们使用了相当宽的光束(20)。在典型的LVCSR设置中,光束会小得多(例如约13)。
再次查看日志文件的顶部,然后将焦点放在命令行上。可选参数在位置参数之前(这是必需的)。类型
gmm解码更快
查看用法消息,并将参数与您在日志文件中看到的匹配。回想一下,“ rspecifier”是指定如何读取表的那些字符串之一,而“ wspecifier”指定了如何编写表的字符串之一。仔细研究这些论点,并试图弄清它们的含义。查看与功能相对应的rspecifier,并尝试理解它(该功能内部有空格,因此Kaldi将其打印出来并用单引号引起来,以便将其粘贴到shell中,程序将按预期运行)。
单音系统现已完成,我们将在教程的下一步中进行三音训练和解码。
在运行triphone系统构建时,我们将花一些时间浏览一下代码的某些部分。从本教程的这一部分中可以得到的主要内容是关于代码的组织方式和依赖结构的概念。以及修改和调试代码的经验。如果您想更深入地了解它的代码,建议您遵循主文档页面上的链接,该页面上有按主题组织的更详细的文档。
通用工具
转到顶级目录(我们称为kaldi-1),然后进入src /。首先查看文件base / kaldi-common.h(不要遵循本文档中的链接;请从Shell或从编辑器进行查看)。#包括base /目录中的许多内容,几乎每个Kaldi程序都使用这些内容。您通常可以从文件名中猜测提供的内容的类型:错误记录宏,typedef,数学实用程序功能(例如随机数生成)和其他#define等内容。但是,这是一组简化的实用程序。在UTIL /共utils.h有一个更完整的组,包括命令行解析和I / O函数手柄延伸的文件名,如管道。花几秒钟浏览util / common-utils.h并查看它包含的内容。之所以将实用程序的子集隔离到base /目录中,是为了使矩阵/目录的依赖关系最小化(它本身很有用);matrix /目录仅取决于base /目录。查看matrix / Makefile并搜索base /以了解其指定方式。查看Makefiles中的这种规则可以使您对工具箱的结构有一些了解。
矩阵库(以及修改和调试代码)
现在查看文件matrix / matrix-lib.h。查看它包含哪些文件。概述了矩阵库中的各种事物。该库基本上是BLAS和LAPACK的C ++包装,如果这对您来说意味着什么(如果没有,请不用担心)。文件sp-matrix.h和tp-matrix.h分别与对称打包矩阵和三角形打包矩阵有关。快速扫描文件matrix / kaldi-matrix.h。这将使您了解矩阵代码的外观。它由代表矩阵的C ++类组成。我们在此处提供有关矩阵库的迷你教程, 如果你感兴趣。您可能会注意到代码中的注释样式似乎很奇怪,注释以三个斜杠(///)开头。这些类型的推荐,并禁止以
/ **
,由自动生成文档的Doxygen软件解释。它还会生成您现在正在阅读的页面(此类文档的来源在src / doc /中)。
在这一点上,我们希望您修改代码并进行编译。我们将在文件matrix / matrix-lib-test.cc中添加一个测试函数。如前所述,测试程序设计为在出现问题时以非零状态中止或退出。
我们将为功能Vector :: AddVec添加一个测试例程。此函数将一个向量与某些向量相加恒定时间。通读下面的代码,并尝试尽可能多地理解它(请注意:我们已在代码中故意插入了两个错误)。如果您不熟悉模板,则可能很难理解。我们试图尽可能避免使用模板,因此在不了解模板编程的情况下,仍然可以理解Kaldi的大部分内容。
template void UnitTestAddVec(){ //注意:实例化时,实数将为float或double。 int32 dim = 1 + Rand()%10; Vector v(dim); w(暗); //两个大小相同的向量。 v.SetRandn(); w.SetRandn(); Vector w2(w); // w2是w的副本。 实数f = RandGauss(); w.AddVec(f,v); // w <-w + fv for(int32 i = 0; i
UnitTestAddVec ();
在函数中的哪个位置添加都没关系。然后输入“ make test”。应该有一个错误(分号应该是逗号);修复它,然后重试。现在键入“ ./matrix-lib-test”。这会因断言失败而崩溃,因为单元测试代码中还有另一个错误。接下来,我们将对其进行调试。类型
gdb ./matrix-lib-test
(如果您使用的是cygwin,则现在应在gdb提示符下输入“ break __assert_func”)。输入“ r”。当它崩溃时,它将调用abort(),调试器会捕获该异常。键入“ bt”以查看堆栈跟踪。通过键入“ up”来向上调整堆栈,直到进入测试功能。当您在正确的位置时,您应该看到如下输出:
#5 0x080943cf在matrix-lib-test.cc:2568的kaldi :: UnitTestAddVec ()中 2568 AssertEqual(a,b); //如果不等于
如果走得太远,可以键入“向下”。然后键入“ p a”和“ p b”以查看a和b的值(“ p”是“ print”的缩写)。您的屏幕看起来应该像这样:
(gdb)pa 5美元= -0.931363404 (gdb)铅 $ 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 0x80943b4处的断点1:文件matrix-lib-test.cc,第2568行。(4个位置)
然后运行该程序(类型“ r”),并在其中中断时,使用“ p”命令查看表达式的值。要继续,请键入“ c”。由于它处于循环中,因此它将一直停在那里。键入“ d 1”删除断点(假设它是第一个断点),然后键入“ c”继续。该程序应运行到最后。键入“ q”退出调试器。如果您需要调试采用命令行参数的程序,则可以执行以下操作:
gdb --args kaldi程序arg1 arg2 ... (gdb)r ...
或者您可以不带参数调用gdb,然后在提示符下键入“ r arg1 arg2 ...”。
完成并编译后,键入
git diff
看看您做了什么更改。如果您为Kaldi项目做出贡献并计划在不久的将来向我们发送代码,则可能希望按照所述将它们提交到一个分支,以便以后可以生成一个干净的GitHub pull request。我们建议您即使不直接进行更改也要熟悉Git分支。Git是一个功能强大的工具,可以维护您以及您可能贡献的本地代码更改。
声学建模代码
接下来查看gmm / diag-gmm.h(此类存储高斯混合模型)。DiagGmm类可能看起来有些混乱,因为它具有许多不同的访问器功能。搜索“私有”并查看类成员变量(根据Kaldi样式,它们始终以下划线结尾)。这应该清楚说明我们如何存储GMM。这只是单个GMM,而不是GMM的全部集合。看看gmm / am-diag-gmm.h; 此类存储GMM的集合。请注意,它不会继承任何东西。搜索“私有”,您可以看到成员变量(其中只有两个)。您可以从中了解该类的简单程度(其他所有内容均由各种访问器和便捷功能组成)。一个自然的问题是:转换在哪里,决策树在哪里,HMM拓扑在哪里?所有这些事情都与声学模型保持分离,因为研究人员可能希望在保持系统其余部分不变的同时替换声学可能性。稍后我们将介绍其他内容。
特征提取代码
接下来查看feat / feature-mfcc.h。专注于MfccOptions结构。struct成员使您了解MFCC功能提取支持哪些类型的选项。请注意,某些结构成员是选项结构本身。查看注册功能。这是Kaldi选项类的标准。然后查看featbin / compute-mfcc-feats.cc(这是一个命令行程序)并搜索Register。您可以看到在何处调用了选项结构的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
您从中获得的唯一有用信息是它不会崩溃。请尝试删除“ =”符号或缩写选项名称或更改参数数量,以查看其是否失败并显示使用情况消息。
声学决策树和HMM拓扑代码
接下来查看tree / build-tree.h。找到BuildTree函数。这是用于构建决策树的主要顶级功能。注意,它返回一个类型为EventMap的指针。此类型存储从一组(键,值)对到整数的函数。它在tree / event-map.h中定义。键和值都是整数,但是键代表语音上下文位置(通常为0、1或2),而值代表电话。还有一个特殊键-1,它大致表示HMM中的位置。转到实验目录(../egs/rm/s5),我们将研究如何构建树。BuildTree函数的主要输入是BuildTreeStatsType类型,其类型如下:
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的统计信息。
做
较少exp / tri1 / log / acc_tree.log
该文件中没有太多信息,但是您可以看到命令行。该程序为每个可见的三音机上下文的每个HMM状态(实际上是pdf类)累积单高斯统计量。这些–ci-phones选项是为了让它知道避免为不同的电话上下文(如静音)积累单独的统计信息,例如我们不想依赖于上下文的静音(这是一种优化;如果没有此选项,它将起作用)。该程序的输出可以认为是上面讨论的BuildTreeStatsType类型,尽管为了阅读它,我们必须知道它是什么具体类型。
做
较少exp / tri1 / log / train_tree.log
该程序进行决策树聚类;它读取输出的统计信息。它基本上是上面讨论的BuildTree函数的包装器。它在决策树群集中提出的问题是自动生成的,如您在脚本step / train_tri1.sh中所看到的(查找程序cluster-phones和compile-questions)。
接下来看hmm / hmm-topology.h。HmmTopology类为许多电话定义了一组HMM拓扑。通常,每个电话可以具有不同的拓扑。拓扑包括用于初始化的“默认”过渡。在标题顶部的扩展注释中查看示例拓扑。有一个标记(注意:与HTK文本格式一样,此文件看起来像XML,但实际上不是XML)。始终与HMM状态()相同。通常,不必如此。这是一种在不同的HMM状态之间强制分配分配的机制。如果要创建5个或更有趣的过渡模型,这可能很有用。
萍水相逢逢萍水,浮萍之水水浮萍!