Kaldi的基本框架和逻辑
原先阅读代码时的笔记整理,后面需要进行完善和删改,大部分资料来源于Kaldi官方帮助手册
# 特征提取 (opens new window)
kaldi只能处理.wav/.pcm格式的音频文件,其他的格式需要被转换,比如sphere文件,可以用sph2pipe来处理;直接输入compute-mfcc-feats
及compute-plp-feats
而不附带任何参数的话,会显示帮助文档
# MFCC
compute-mfcc-feats
需要输入两个参数,一个是读取wav文件,一个是写fea文件程序中并没有加一阶及二阶差分,但是对于多信道的数据,可以选择信道,如:
-channel=0
参数设置
25ms为一帧,10ms的帧移;
最低采样频率20Hz,最高采样频率比奈奎斯特频率小一些;
提取数据,进行可选的抖动、预加重和消除直流偏移,并将其乘以窗口函数;
计算出此时的能量(如果使用的是log-energy而不是C0);
做FFT并计算频谱能量;
计算每个滤波器中的能量;
计算log能量并进行余弦变换,选择前13维;
选择性做同态滤波:这只是系数的缩放,确保它们有一个合理的范围。
处理方式与HTK有很多不同,但是可以设置
-htk-compat=true
来获得与HTK差不多的结果只是进行标准的处理,所以也没有归一化的操作,但是
-subtract-mean
可以去除均值
# IO机制(命令行角度)
# 非表格型IO
这指的是只包含一个或两个对象的文件或流(例如声学模型文件;转换矩阵),而不是由字符串索引的对象集合。
- Kaldi文件格式默认是二进制的,但是如果你提供标志
--binary=false
,程序将输出非二进制的。 - 许多对象都有相应的“copy”程序,例如
copy-matrix
或gmm-copy
,可以使用--binary=false
标志将其转换为文本形式,例如copy-matrix --binary=false foo.mat -
。 - 在磁盘上的文件和内存中的c++对象之间通常存在一对一的对应关系,例如,虽然有些文件包含多个对象(例如:对于声学模型文件,通常是一个
TransitionModel
对象,然后是一个声学模型),但仍然是一个float矩阵。 - Kaldi程序通常知道它们希望读取哪种类型的对象,而不是从流中计算出来。(提前申明文件类型)
- 与perl类似,文件名可以替换为
-
(用于标准输入/输出)或诸如|gzip -c >foo.gz
或gunzip -c foo.gz|
之类的字符串。 - 对于读取文件,我们还支持
foo:1045
,意思是文件foo中的字符偏移量为1045。 - 为了引用扩展文件名的概念,我们通常使用特殊术语“rxfilename”来表示描述要读取的流的字符串(即文件、流或标准输入),用“wxfilename”表示描述输出流的字符串。有关详细信息,请参阅扩展文件名:rxfilenames和wxfilenames。
# 表格型IO
Kaldi有特殊的I/O机制来处理由字符串索引的对象集合。这方面的例子有以uttID为索引的特征矩阵,或以spkID为索引的说话者自适应变换矩阵。索引集合的字符串必须是非空的,并且没有空格。
Table可以以两种形式存在:“archive”或“script file”。不同之处在于archive实际上包含数据,而script file指向数据的位置。
从表中读取数据的程序需要一个我们称为“rspecifier”的字符串,它表示如何读取索引数据,而向表中写入数据的程序需要一个我们称为“wspecifier”的字符串。这些字符串指定是期望的script file还是archive、文件位置以及各种选项。常见的rspecifier类型包括“ark:-”,表示从标准输入中读取数据作为archive,或者scp:foo.scp
,意思是脚本文件foo.scp
指示从何处读取数据。需要记住的是:
冒号后面的部分被解释为wxfilename或rxfilename(在非表I/O中),这意味着支持管道和标准输入/输出。
一个表总是只包含一种类型的对象(例如,一个float矩阵)。
你可能会看到rspecifier和wspecifier的选项,主要是:
- 在rspecifier中,
ark,s,cs:-
表示当我们阅读(在这种情况下从标准输入)我们预计其内容是经过排序的(,s),并且我们断言,他们将被按顺序访问(,cs),这意味着我们知道程序将按顺序访问它们(如果不满足这些条件,程序会崩溃)。这允许Kaldi模拟随机访问,而不占用大量内存。 - 对于那些不是太大的数据,并且不方便确保排序顺序的数据(例如,针对说话人自适应的转换),省略
,s,cs
并没有什么害处。 - 通常,使用多个rspecifier的程序会迭代第一个rspecifier中的对象(顺序访问),然后随机访问后面的对象,所以第一个rspecifier通常不需要
,s,cs
。 - 在
scp,p: foo.scp
中,,p
表示如果某些引用的文件不存在,我们不应该崩溃(对于archive,如果archive被损坏或截断,,p
将防止崩溃)。 - 书写的时候,
,t
表示text模式,比如ark,t:-
,二进制命令行选项对archive没有影响。
- 在rspecifier中,
script file的格式为
<key> <rspecifier|wspecifier>
,如,utt1 /foo/bar/utt1.mat
;rspecifier或wspecifier可以包含空格,如,utt1 gunzip -c /foo/bar/utt1.mat.gz|
archive的格式为
<key1> <object1> <newline> <key2> <object2> <newline> ...
ark文件无法打印到标准输出上查看
archive可能是相连的,它们仍然是有效的archive,但要注意连接的顺序,如果你要排序好的样子,避免使用
cat a/b/*.ark
虽然不经常使用,但是script file可以用于输出,如,如果我们向wspecifier写
scp:foo.scp
,当程序试图写入utt1,它会在foo.scp中寻找类似"utt1 some_file"这样的行,并将写入“some_file”。如果没有这样的行,它将崩溃。可以同时对archive和script file进行写入,如:
ark,scp:foo.ark,foo.scp
。script file将被写入类似utt1 foo.ark:1016
这样的内容(它指向archive中的字节偏移量)。这在以随机顺序或部分方式访问数据时非常有用,但是您不希望生成大量的小文件。可以欺骗archive机制对单个文件进行操作,如:
echo '[ 0 1 ]' | copy-matrix 'scp:echo foo -|' 'scp,t:echo foo -|'
。首先,如果bar.scp文件中只有这样的行,如"foo -",rspecifier(即,scp:echo foo -|
)等价于scp:bar.scp,这告诉它从标准输入中读取由“foo”索引的对象。同样的,对于wspecifier(即,scp,t:echo foo -|
)来说,它将“foo”的数据写入标准输出。这种伎俩不应被滥用。在这种特殊情况下,这是不必要的,因为我们已经使copy-matrix
程序直接支持非表I/O,所以您可以只写copy-matrix - -
。如果你不得不经常使用这个技巧,最好是修改相关的程序。如果想从archive中提取一个成员member,可以用wspecial
scp:
中的,p
选项,这样可以只写出这一个对象,并且忽略script file中的其他元素。假设你要的关键词是foo_bar
,还有一个名为some_archive.ark
的archive文件,应如下输入:copy-matrix 'ark:some_archive.ark' 'scp,t,p:echo foo_bar -|'
在某些情况下,archive的读取代码允许有限的类型转换,例如在矩阵中使用float和double,或者在lattices中使用Lattice和CompactLattice。
# 带范围的表格型IO
现在可以从scp文件中指定矩阵的行范围和列范围。当您转储功能文件时,通常会将它们表示为scp文件,如下所示:
utt-00001 /some/dir/feats.scp:0
utt-00002 /some/dir/feats.scp:16402
...
2
3
您可以通过添加类似于MATLAB的格式(除了使用从零开始的索引)的行和列范围来修改这个scp文件。例如,如果你把它修改为:
utt-00001 /some/dir/feats.scp:0[0:9]
utt-00001 /some/dir/feats.scp:0[10:19]
...
2
3
前两行表示utt-00001的第0行到第9行,第10行到第19行。您可以用类似的方式表示列索引:
utt-00001 /some/dir/feats.scp:0[:,0:12]
utt-00001 /some/dir/feats.scp:0[:,13:25]
...
2
3
将是该文件的0到12列和13到25列。您还可以有行和列索引的组合:例如,
utt-00001 /some/dir/feats.scp:0[10:19,0:12]
# utt2spk与spk2utt的映射
这些文件用于说话人自适应,例如查找哪个说话者对应于某个话语,或者遍历说话者。由于主要与Kaldi示例脚本的设置方式和我们将数据分割成多个块的方式有关,因此确保utt2spk映射中的说话者处于有序状态非常重要(请参阅数据准备)。无论如何,这些文件实际上被当作archive,因此您将看到命令行选项,如-utt2spk=ark:data/train/utt2spk
。您将看到这些文件符合通用的归档格式:<key1> <data> <newline> <key2> <data> <newline>
,在本例中,数据是文本形式的。在代码层,将utt2spk文件视为包含字符串的表,将spk2utt文件视为包含字符串列表的表。
# 并行处理
Kaldi被设计成与Sun GridEngine等软件或其他基于类似原理的软件配合得最好;如果多个机器要在集群中一起工作,那么它们需要访问共享的文件系统,比如基于NFS的文件系统。但是,可以轻松地将Kaldi配置为在一台机器上运行。
如果看一个顶层脚本,如 egs/wsj/s5/run.sh
,会看到这样的命令
steps/train_sat.sh --cmd "$train_cmd" \
4200 40000 data/train_si284 data/lang exp/tri3b_ali_si284 exp/tri4a
2
在 run.sh
脚本的开头,将看到它source
了 cmd.sh
脚本:
. ./cmd.sh
在 cmd.sh
脚本中,可以看到
export train_cmd="queue.pl -l arch=*64"
如果您没有GridEngine,或者您的队列配置与CLSP@JHU不同,那么您将更改这个变量。要在一台机器上本地运行所有内容,可以设置export train_cmd=run.pl
。
在 steps/train_sat.sh
中的变量 cmd
是用–cmd
传递进来的,如,本脚本中的 queue.pl -l arch=*64
,并且在脚本中将看到如下命令:
$cmd JOB=1:$nj $dir/log/fmllr.$x.JOB.log \
ali-to-post "ark:gunzip -c $dir/ali.JOB.gz|" ark:- \| \
weight-silence-post $silence_weight $silphonelist $dir/$x.mdl ark:- ark:- \| \
gmm-est-fmllr --fmllr-update-type=$fmllr_update_type \
--spk2utt=ark:$sdata/JOB/spk2utt $dir/$x.mdl \
"$feats" ark:- ark:$dir/tmp_trans.JOB || exit 1;
2
3
4
5
6
所发生的是命令$cmd
(例如queue.pl
或run.pl
)正在被执行;它负责生成作业并等待它们完成,如果出现错误则返回非零状态。这些命令(还有其他一些命令称为slur .pl
和ssh.pl
)的基本用法如下:
queue.pl <options> <log-file> <command>
其中,最简单的命令是:
run.pl foo.log echo hello world
(我们使用run.pl作为示例,因为它可以在任何系统上运行,不需要GridEngine)。可以运行一维作业数组,例如:
run.pl JOB=1:10 foo.JOB.log echo hello world number JOB
这些程序将用该范围内的数字替换命令行中JOB的任何实例,因此请确保工作目录不包含字符串JOB,否则可能会发生糟糕的事情。你甚至可以通过适当的引用或转义来提交带有管道和重定向的作业:
run.pl JOB=1:10 foo.JOB.log echo "hello world number JOB" \| head -n 1 \> output.JOB
在这种情况下,实际执行的命令将类似于:
echo "hello world number JOB" | head -n 1 > output.JOB
如果您想查看实际执行的是什么,可以查看foo.1这样的文件。日志,你会看到以下内容:
# echo "hello world number 1" | head -n 1 > output.1
# Started at Sat Jan 3 17:44:20 PST 2015
#
# Accounting: time=0 threads=1
# Ended (code 0) at Sat Jan 3 17:44:20 PST 2015, elapsed time 0 seconds
2
3
4
5
- 可以看到首行显示的是实际命令,而不包含并行命令
# 并行化工具的通用接口
在本节中,我们将讨论并行化工具的共性。它们都被设计成可以在命令行上进行互换,这样,针对这些并行化工具之一进行测试的脚本就可以用于任何并行化工具;通过将$cmd变量设置为不同的值,可以切换到使用另一个变量。
这些工具的基本用法是,
queue.pl <options> <log-file> <command>
我们接下来要说的也适用于run.pl, ssh.pl和slur .pl。
<options>可能包括以下部分或全部:
- 作业范围说明符(例如
JOB=1:10
)。该名称仅按惯例为大写,并可能包括下划线。起始索引必须为1或更多;这是GridEngine的一个限制。 - 任何看起来会被GridEngine接受为qsub选项的内容。例如,-l arch=64,或-l mem_free=6G,ram_free=6G,或-pe smp 6。为了兼容性,queue.pl之外的脚本将忽略这些选项。
- 新型选项,如
-mem 10G
(见下文)。
<log-file>只是一个文件名,它对于数组作业必须包含数组的标识符(例如exp/foo/log/process_data.JOB.log)。
<command>基本上可以是任何东西,包括可能被shell解释的符号,但是当然,queue.pl
不能处理首先被bash解释的东西。例如,这是错误的:
queue.pl test.log echo foo | awk 's/f/F/';
因为queue.pl
甚至不会在管道符号之后看到参数,而是将其标准输出通过管道输送到awk命令中。相反,你应该写
queue.pl test.log echo foo \| awk 's/f/F/';
您需要转义或引用管道符号,以及诸如;
和>
之类的内容。如果<command>中的一个参数包含一个空格,那么queue.pl
将假定您引用它是有原因的,并在将它传递给bash时为您引用它。默认情况下,它使用单引号,但如果字符串本身包含单引号,它则使用双引号。这通常是我们想要的。从您执行queue.pl
的shell中获得的PATH变量将被传递到将要执行的脚本中,为了确保您已经获得了所需的所有内容,./path.sh
文件也将被获取。这些命令将使用bash执行。
# 新型选项(统一接口)
在最初编写Kaldi时,当我们需要指定诸如内存需求之类的东西时,我们让示例脚本传入一些选项,如-l ram_free=6G,mem_free=6G
给queue.pl
。因为像steps/train_sat.sh
这样的脚本无法对如何配置GridEngine或我们是否在使用GridEngine做出任何假设,所以必须从脚本的最外层传递这些选项,这很尴尬。我们最近为并行化脚本定义了一个“新样式的接口”,这样它们都可以接受以下类型的选项(示例如下):
--config conf/queue_mod.conf
--mem 10G
--num-threads 6
--max-jobs-run 10
--gpu 1
2
3
4
5
配置文件指定如何将新样式的选项转换为GridEngine(或您选择的网格软件)可以解释的形式。目前只有queue.pl
实际解释了这些选项;其他脚本忽略它们。我们的计划是逐步修改脚本/在必要时使用新样式的选项,并通过使用配置文件(在可能的情况下)对其他类型的网格软件使用queue.pl
。
# 使用特定脚本的并行化
在本节中,我们将解释特定于各个并行化脚本的内容。
run.pl
如果用户没有安装GridEngine, run.pl
是一个选择。这个脚本非常简单;它在本地机器上运行您请求的所有作业,如果您使用作业范围说明符(如JOB=1:10
),它就会并行地运行这些作业。在本地机器上并行。它不会试图跟踪有多少cpu可用或您的机器有多少内存。因此,如果您使用run.pl
在更大的网格上运行设计为与queue.pl
一起运行的脚本,您可能会耗尽计算机的内存或使作业超载。我们建议您研究正在运行的脚本,并特别小心地解码在后台运行的脚本(使用&
)和使用大量作业的脚本,例如-nj=50
。一般来说,您可以在不影响结果的情况下降低-nj
选项的值,但是在某些情况下,给多个脚本的-nj
选项必须匹配,否则后期将崩溃。
run.pl
将忽略除作业范围说明符JOB以外的所有给它的选项。
# 文件结构 (opens new window)
在进入示例脚本之前,让我们先花几分钟看看Kaldi发行版中还包括哪些内容。转到kaldi-1目录并列出它。有一些文件和子目录。重要的子目录是“tools/”、“src/”和“egs/”,我们将在下一节中讨论它们。现在,我们将给出“tools/”和“src/”的概述。
# tools/
“tools/”目录是我们安装Kaldi所需的依赖的地方,将目录更改为tools/
并列出它。您将看到各种文件和子目录,其中大部分已经用make命令安装了。快速查看文件INSTALL,这个文件给出了如何安装这些工具的说明。
# INSTALL文件内容
extras/check_dependencies.sh# 检查依赖
CXX=g++-4.8 extras/check_dependencies.sh # 如果默认的C++编译器不行,就用该命令检查依赖
make# 默认安装ATLAS headers, OpenFst, SCTK和sph2pipe,OpenFst需要一个相对新的C++11编译器
make CXX=g++-4.8# 如果默认的编译器级别不够,就自己指定版本
make -j 4# 并行编译,用4个CPUs
2
3
4
5
6
在extras/目录下,还有各种脚本可以安装,可用于个人示例脚本。如果一个示例脚本需要你去运行这些脚本中的任何一个,他会告诉你该怎么做。
# 各种包的作用
- OpenFst 状态机
- IRSTLM 语言模型工具
- SRILM 比 IRSTLM 好的语言模型工具
- sph2pipe 将 sph 文件转换为 其他格式比如 wav等。 LDC 数据相关的脚本需要该工具。
- sclite 打分工具,也可以使用简单程序比如 compute-wer.cc
- ATLAS 线代库
- CLAPACK 线代库
- OpenBLAS 线代库
2
3
4
5
6
7
8
9
没有安装Openfst,因此下文暂时无意义
其中一个重要的子文件夹是OpenFST,切换到openfst/, 这是一个链接到实际目录的软链接,它有一个版本号。列出openfst目录。如果安装成功,将有一个包含安装的二进制文件的bin/目录和一个包含库的lib/目录(我们需要这两个目录)。最重要的代码在include/fst/目录中。如果你想深入了解Kaldi,你需要了解OpenFst。那么最好从下面的网站开始:http://www.openfst.org/
现在,查看 include/fst/fst.h
文件。这包括一些抽象FST类型的声明。您可以看到这里涉及了很多模板。如果您不喜欢模板,那么您可能很难理解这段代码。
将目录更改为bin/,或将其添加到您的路径中。我们将在这里 (opens new window)执行一些简单的示例指令。
将以下命令粘贴到shell中:
# 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 <<EOF
0 1 a x .5
0 1 b y 1.5
1 2 c z 2.5
2 3.5
EOF
2
3
4
5
6
7
8
9
10
以下命令创建符号表;把它们也粘到shell里。
cat >isyms.txt <<EOF
<eps> 0
a 1
b 2
c 3
EOF
cat >osyms.txt <<EOF
<eps> 0
x 1
y 2
z 3
EOF
2
3
4
5
6
7
8
9
10
11
12
13
注意:如果您的路径上没有当前目录,您可能需要输入以下步骤:
export PATH=.:$PATH
接下来创建一个二进制格式的FST:
fstcompile --isymbols=isyms.txt --osymbols=osyms.txt text.fst binary.fst
执行一个示例命令:
fstinvert binary.fst | fstcompose - binary.fst > binary2.fst
结果是WFST, binary2.fst,应该类似于binary.fst,但是有两倍的重量。你可以打印出来看看:
fstprint --isymbols=isyms.txt --osymbols=osyms.txt binary.fst
fstprint --isymbols=isyms.txt --osymbols=osyms.txt binary2.fst
2
这个例子是从www.openfst.org上的一个较长的教程修改而来的。完成这些之后,敲入以下文字进行清理:
rm *.fst *.txt
# src/
将目录更改回顶层(kaldi -1),并将其更改为src/.的目录列表。您将看到一些文件和大量的子目录。看看Makefile。在顶部,它设置变量SUBDIRS。这是包含代码的子目录列表。注意其中一些以“bin”结尾。这些文件包含可执行文件(代码和可执行文件在同一个目录中)。其他目录包含内部代码。
您可以看到Makefile中的一个目标是“test”。输入“make test”。该命令进入各个子目录并在其中运行测试程序。所有的测试都应该成功。如果你觉得幸运,你也可以输入“make valgrind”。这将使用内存检查器运行相同的测试,并且需要更长的时间,但是会发现更多的错误。如果这不起作用,那就忘了它;现在还不重要。如果花的时间太长,用ctrl-c停止它。
将目录更改为base/。看看Makefile。注意到这样一行:
include ../kaldi.mk # 我没有找到,服务器上也没有!!!
这几行代码包括了当调用子目录中的Makefile时的“../kaldi.mk”文件(就像c# include指令一样)。请看../kaldi.mk文件,它将包含一些与valgrind(用于内存调试)相关的规则,然后是一些特定于系统的配置,例如CXXFLAGS。查看是否有-O选项(例如-O0)。在缺省情况下,-O0和-DKALDI_PARANOID标志是禁用的,因为它们会降低速度(您可能希望启用它们以进行更好的调试)。再次查看base/Makefile。顶部的语句“all:”告诉Make,“all”是顶级目标(因为在kaldi中有目标,而我们不希望这些成为顶级目标)。因为“all”的依赖关系依赖于后面定义的变量,所以我们有另一个语句(目标在default_rules.mk中被定义),在该语句中我们定义了“all”所依赖的内容。寻找它。从“clean”开始,还定义了其他几个目标。寻找他们。要生成“clean”,您需要键入“make clean”。您不会从命令行调用目标.valgrind;您将键入“make valgrind”(该目标在kaldi.mk中定义)。调用所有这些目标,即键入“make clean”并对其他目标执行相同的操作,并注意执行此操作时发出的命令。
在base/ directory中的Makefile中:选择TESTFILES中列出的一个二进制文件,然后运行它。简要地查看相应的.cc文件。math one是一个很好的例子(注意:这排除了Kaldi中的大多数数学函数,它们是与矩阵向量相关的函数,位于../matrix/中)。注意,宏KALDI_ASSERT中有很多断言。这些测试程序被设计为在出现问题时以错误状态退出(它们不应该依赖于对输出的人工检查)。
源码目录结构分析:kaldi 源码阅读(二) - 代码结构分析 (opens new window)
egs/ 开放数据集合,主要目录 wsj, rm
|- README.txt
src/ 代码主目录
|- configure 构建 kaldi.mk 文件(c库,编译优化参数等)
|- Makefile 组织整体代码依赖相关内容
|- ……
tools/ 基础工具包
|- atlas 数学算法库
|- clpack cl线代库加速包
|- srilm 语言模型生成工具
|- openfst 加权有限状态机(Makefile文件中自动下载并编译)
|- sctk Speech Recognition Scoring Toolkit(Makefile文件中自动下载并编译)
|- sph2pipe SPHERE 文件转换(其他音频)工具(Makefile文件中自动下载并编译)
|- openblas 开源blas库(Makefile文件中自动下载并编译)
|- extras 扩展工具包
2
3
4
5
6
7
8
9
10
11
12
13
14
15
常见文件格式:
标记名称 | 说明 |
---|---|
utterance-id | 发音编号, 可以是任意的文本字符串 |
speaker-id | 说话人编号, 常作为发音编号的前缀 |
record-id | 和在“wav.scp”中使用的是同一个标识字符串, 与 uttid 一样 |
segment-begin/segment-end | 以秒为单位,它们指明了一段发音在一段录音中的时间偏移量 |
现在可以查看具体文件的内容
文件名称 | 内容格式 |
---|---|
text | < uttid > < word > |
wav.scp | < uttid > < utter_file_path > |
utt2spk | < uttid > < speakid > |
spk2utt | < speakid > < uttid > |
feats.scp | < uttid > < extended-filename-of-features > |
segments | < uttid > < recid > < segbegin > < segend > |
在每一个 feats.scp 特征文件中保存的都是Kaldi格式的矩阵。在这个例子中,矩阵的维度是13(译者注:即列数;行数则 和你的文件长度有关,标准情况下帧长20ms,帧移10ms,所以一行特征数据对应10ms的音频数据)。比如:
s5# head -3 data/train/cmvn.scp
// 表示 2001-A 对应的特征从 cmvn_train.ark 文件的 7 字节开始读取数据
2001-A /home/xxxx/kaldi/egs/swbd/s5/mfcc/cmvn_train.ark:7
// 表示 2001-B 对应的特征从 cmvn_train.ark 文件的 253 字节开始读取数据
2001-B /home/xxxx/kaldi/egs/swbd/s5/mfcc/cmvn_train.ark:253
// 表示 2005-A 对应的特征从 cmvn_train.ark 文件的 499 字节开始读取数据
2005-A /home/xxxx/kaldi/egs/swbd/s5/mfcc/cmvn_train.ark:499
2
3
4
5
6
7
查看/src/featbin/中的内容
函数名称 | 作用 |
---|---|
# egs/
先查看该文件夹下的README,需要LDC数据,如果没有,也尽量看完不需要数据的部分,这也能获得一些收获。
cmd.sh # 并行执行命令,通常分 run.pl, queue.pl 两种
config # 参数定制化配置文件, mfcc, decode, cmvn 等配置文件
local # 工程定制化内容
path.sh # 环境变量相关脚本
run.sh # 整体流程控制脚本,主入口脚本
steps # 存放单一步骤执行的脚本
utils # 存放解析文件,预处理等相关工具的脚本
2
3
4
5
6
7
如上述目录结构分析类似, 与并行计算相关的内容均在utils/parallel
目录下
# 文件及数据查看
参考资料:kaldi常用的工具 (opens new window)
查看生成的特征文件是最常用的,比如 MFCC FBANK , 我们可以把特征提取出来用到其他的地方,文件后缀是ark
~/kaldi/src/featbin/copy-feats ark:raw_mfcc_test_hires.1.ark ark,t:- | head
这里需要注意的是在文件前边加ark关键字,告诉copy-feats是什么格式的,还有后面加一个ark,t:- 表示输出格式为t文本,否则默认是二进制。如果我们需要加差分该怎么做呢?
~/kaldi/src/featbin/copy-feats ark:raw_mfcc_test_hires.1.ark ark:- | ~/kaldi/src/featbin/add-deltas ark:- ark,t:- | head
如果要想在mfcc.sh中直接输出只带一阶差分的特征:
$cmd JOB=1:$nj $logdir/make_mfcc_${name}.JOB.log \
compute-mfcc-feats $vtln_opts --verbose=2 --config=$mfcc_config \
scp,p:$logdir/wav_${name}.JOB.scp ark:- \| \
copy-feats --compress=$compress ark:- ark:- \| \
add-deltas --delta-order=1 ark:- \
ark,scp:$mfccdir/raw_mfcc_$name.JOB.ark,$mfccdir/raw_mfcc_$name.JOB.scp \
|| exit 1;
2
3
4
5
6
7
8
# 数据转换
参考资料:Kaldi中特征文件格式的转换 (opens new window)
ark文件与txt文件互相转换
需要用到copy-feats命令
copy-feats ark:train.ark ark,t:/train.txt# ark转化为txt copy-feats ark,t:train.txt ark:train.ark# txt转化为ark
1
2copy-feats的主要功能是将文件变成数据流,这样方便对数据进行处理。 比如你想查看kaldi中提取的mfcc特征到底是什么样子,同样可以用copy-feats,如:
copy-feats ark:train.ark ark,t:- | less
,通过一个管道用less来查看ark里面的内容。其他的和特征有关的命令还可以在src/featbin/
中自己查看,还有很多很有用的命令。比如copy-matrix
可以把矩阵的特征转化成ark格式。
# trials文件
格式:spk utt target/nontarget
1 spk对应N utts
# Log文件的逻辑
所有由日志消息、警告或错误组成的输出都指向Kaldi程序中的标准错误。这是为了使我们的程序可以在管道中使用,而不会使这些消息与程序的输出“混淆”。生成日志、警告或错误输出的最常见方法是通过宏KALDI_LOG、KALDI_WARN和KALDI_ERR。调用KALDI_ERR宏通常会终止程序(除非捕捉到异常)。一个例子代码片段,说明了所有这三个是如下: