从命令行角度理解Kaldi的I/O机制

本页从用户命令行的角度来描述Kaldi的I/O机制。

代码层的分析见 Kaldi I/O mechanisms

Overview

Non-Table I/O

首先描述“non-table” I/O。这指的是只包含一个或两个对象的文件或数据流(e.g.声学模型文件;变换矩阵),而不是一系列由字符串索引的对象。

  • Kaldi 默认文件格式是二进制,但是程序可以输出非二进制格式,如果你设定标志-binary=false
  • 很多对象都有相应的“copy”程序,e.g. copy-matrix或 gmm-copy,可以用标志-binary=false来转换成文本形式输出,e.g.“copy-matrix --binary=false foo.mat -”
  • 通常磁盘上的一个文件和内存中的一个C++对象有一一对应关系,e.g.一个浮点型矩阵,尽管有些文件会包含多于一个的对象(典型的例子:声学模型文件中,通常是一个TransitionModel对象,接着是一个声学模型)
  • Kaldi 程序一般知道它要读的对象的类型,而不是试图从数据流中去判断
  • 和 perl类似,一个文件名可以用-(代表标准输入输出)替代,或者是一个字符串,比如“|gzip -c >foo.gz”或“gunzip -c foo.gz|”
  • 读文件时,我们支持这样的定义,如 foo:1045,表示 foo文件内的1045字符偏移
  • 为了指示我们扩展文件名的概念,我们一般用 rxfilename来表示一个被读的文件流(i.e.一个文件,流或标准的输入),用 wxfilename来表述一个输出流。更多细节见 Extended filenames: rxfilenames and wxfilenames

为了说明上述概念,确保 $KALDI_ROOT/src/bin 在你的工作路径中,其中 $KALDI_ROOT是 Kaldi 库的路径,键入以下内容:

echo '[ 0 1 ]' | copy-matrix - -

它会打印出日志信息和对应于该矩阵的二进制数据。现在试试:

echo '[ 0 1 ]' | copy-matrix --binary=false - -

输出将是这样:

# copy-matrix --binary=false - - 
copy-matrix --binary=false - - 
 [
  0 1 ]
LOG (copy-matrix:main():copy-matrix.cc:68) Copied matrix to -

虽然看起来矩阵和日志信息混在一起了,但是日志信息是在标准错误流(stderr)中,并不会被传递给管道;为避免看到日志信息你可以重定向标准错误到 /dev/null,只要在命令行加入 2>/dev/null。

Kaldi 程序可以通过管道连接起来,利用 Kaldi I/O中流就是文件的机制。这里是一个管道的例子:

echo '[ 0 1 ]' | copy-matrix - - | copy-matrix --binary=false - -

这里以文本形式输出矩阵(第一个 copy-matrix命令转换到二进制形式,第二个转换到文本形式)。你可以以更加复杂的方式来完成同样的事情:

copy-matrix 'echo [ 0 1 ]|' '|copy-matrix --binary=false - -'

这里没必要这么做,但是当程序中有多个输入输出时它可能有用,因为这时 stdin或 stdout已经被占用。对 tables 这一点特别有用(见下节)。

Table I/O

Kaldi 在处理由字符串索引的对象集合时有特殊的 I/O机制。这方面的例子有,由 utterance-ids 索引的特征矩阵,或由 speaker-ids 索引的说话人自适应变换矩阵。索引的字符串必须是非空的且不包含空格。见 The Table concept 中更深入的讨论。

表(Table)可以有两种形式:“archive”和“script file”。区别在于,archive包含实际的数据,而 script file存储数据的地址。

从 Tables读数据的程序期望这一个“rspecifier”的字符串,来告诉如何读取索引的数据,写 Tables的程序也需要一个“wspecifier”的字符串来告诉如何写。这些字符串指明了得到的是 script file还是 archive,文件在磁盘的存储位置和一些其他选项。典型的 rspecifier有“ark:-”,意思是从标准输入读入 archive的数据,或“scp:foo.scp”,意思是指定从 foo.scp读数据。要记住几点:

  • 冒号后面的部分被解释成 wxfilename或 rxfilename(同 Non-table I/O),意味着支持管道和标准输入/输出
  • Table 总是只包含一类对象(e.g.一个浮点型矩阵)
  • rspecifiers和 wspecifiers主要的选项有
    • rspecifiers中 ark,s,cs:- 意味着读的时候(这里是从标准输入)我们认为 keys是排好序的(,s),而且声明它们会被顺序访问,(,cs)意味着我们知道程序会顺序访问它们(如果这些条件不满足,程序会报错)。这让 Kaldi 可以模拟随机访问,而无需占用大量内存。
    • 对占空间不大的或是不确定是排好序的(e.g.说话人自适应变换)数据,省略,s,cs也可以
    • 有多个 rspecifiers的程序一般会遍历第一个 rspecifier中的对象(顺序访问),并随机访问后面的。所以“,s,cs”对第一个 rspecifier没有必要
    • 在 scp,p:foo.scp中,,p 意味着即使部分搜索的文件不存在,也不会报错(对 archives,,p阻止了程序崩溃,即使 archive 发生损坏或截断)
    • 写数据时,,t 表示文本模式,e.g. ark,t:-。这里命令行选项-binary对 archives无效
  • script file的格式是“ ”,e.g.“utt1/foo/bar/utt1.mat”。rspecifier 或 wspecifier 可以包含空格,e.g.“utt1 gunzip -c /foo/bar/utt1.mat.gz|”
  • archive 的格式是 ...
  • archives 可以被组合起来,仍然是有效的。但是要注意组合使得顺序。e.g.如果你需要排序的文件,请避免“cat a/b/*.ark”
  • 尽管不太常用,但 script files可以作为输出文件,e.g.如果我们想写入 wspecifier scp:foo.scp,当程序想写入 key utt1时,它会在 foo.scp中寻找“utt1 some_file”这样的行,并把数据写入“some_file”。如果不存在这样的一行,就会报错。
  • 我们可以同时写 archive 和 script。e.g.ark,scp:foo.ark,foo.scp。写入 script file中的行像“utt1 foo.ark:1016”(i.e.它指出了字节偏移)。这在数据被随机访问或分开存储时很有用,而且你并不希望产生很多小文件。
  • 可以利用 archive的机制来操作单个文件。例如,
     echo '[ 0 1 ]' | copy-matrix 'scp:echo foo -|' 'scp,t:echo foo -|'
    
    这里需要解释下,首先,rspecifier“scp:echo foo -|”等效于“scp:bar.scp”,如果 bar.scp中只包含一行“foo -”。意思是从标准输入中读由“foo”索引的对象。同样,对于 wspecifier“scp,t:echo foo-|”,它把“foo”的数据写到标准输出。这一招不应该被过多使用。在这个例子里,这样做并没有必要,因为 copy-matrix 可以直接支持 non-table I/O,所以你也可以写成“copy-matrix - -”。如果你不得不用这样一个技巧,你最好还是去修改相关的程序代码。
  • 如果你只想取出 archive中的一个元素,你可以在 wspecifier“scp:”加入“,p”选项来只写出这一个元素而忽略掉 scp文件中其他元素。假设你想要的“foo_bar”,在一个包含矩阵的 some_archive.ark中,你可以这样用来提取这一元素:
    copy-matrix 'ark:some_archive.ark' 'scp,t,p:echo foo_bar -|'
    
  • 在特定情况下,读 archive的代码允许某些类型转换,如矩阵中的 float和 double类型,网格中的 Lattice和 CompactLattice。

Utterance-to-speaker and speaker-to-utterance maps

很多 Kaldi的程序都会有“utt2spk”或“spk2utt”这样的 utterance-to-speaker 和 speaker-to-utterance 映射文件。这些一般用命令行选项 -uut2spk-spk2utt指定。utt2spk 格式是这样

utt1 spk_of_utt1
utt2 spk_of_utt2
..

spk2utt 格式是这样

spk1 utt1_of_spk1 utt2_of_spk1 utt3_of_spk1
spk2 utt1_of_spk2 utt2_of_spk2
...

这些文件用于说话人自适应,e.g.用于查找某个 utterance属于哪个说话人,或遍历说话人。根据大部分 Kaldi示例脚本的设定和我们分割数据的方式,我们要确保 utterance-to-speaker 映射中的说话人事排好序的(见 Data preparation)。无论如何,这些文件都被当做 archives,所以你可以看到这样的命令行选项-utt2spk=ark:data/train/utt2spk。你可以看到这些文件符合通用的 archive 格式,即“ ”,这里的数据时文本格式的。在代码层,utt2spk 文件被当做一个包含一个字符串的 table,而 spk2utt 文件被当做包含了一系列字符串的 table。