Kaldi学习手记(三): Kaldi 的I/O机制

来源:互联网 发布:网络赚小钱 编辑:程序博客网 时间:2024/06/11 14:02

一、命令行级 I/O 机制

命令行 I/O 是指在 shell 上调用编译好的 Kaldi 工具的方法。比如,如果想查看音频文件的时长,就可以使用这样的命令
wav-to-duration scp:wav.scp ark,t:-
其中,wav-to-duration 是 Kaldi 中的查看时长的工具,后面两个是参数。下面具体介绍命令行级 Kaldi 的 I/O。

非表格型I/O

非表格型的 I/O 一般比较简单,一般是输入输出只包含一个或两个对象的文件或流(比如,声学模型文件,变换矩阵)。使用方式就是 shell 命令的使用方式。其中值得注意的有:
- Kaldi 采用一种扩展的文件名进行输入输出。读取文件的文件名称为 rxfilename , 写入的文件名称为 wxfilename。
- 在 rxfilename/wxfilename 处采用 “-” 来表示标准输入输出。
- rxfilename/wxfilename 处可以使用管道命令,比如先用gunzip将压缩文件解压,再输入到 Kaldi 程序中,即可在文件输入路径处填入 “gunzip -c foo.gz|”。
- rxfilename/wxfilename 后可以通过 “:” 来描述偏移量,如 “foo:1045” 表示从 foo 文件偏移 1045 个字节开始读取。
- 使用 –binary=true/false 来控制是否使用二进制输出。默认是true。
- 有一组 copy* 命令,可以用来查看文件。如,copy-matrix –binary=false foo.mat -。
- Log 信息和输出会混在一起显示,但是 Log 信息是 stderr 上,所以不会传到管道中。可以通过添加 2>/dev/null 命令,将log信息重定向到 /dev/null。

下面列举一些例子:
echo ‘[ 0 1 ]’ | copy-matrix –binary=false - -
将矩阵 [ 0 1 ] 输入到 copy-matrix 中,再显示出来。亦可以将其表示为
copy-matrix –binary=false ‘echo [ 0 1 ]|’ -

表格型I/O

对于一系列数据的集合,比如对应于每一句话的特征矩阵,或者每一条音频文件对应的路径,Kaldi 采用表格形式的数据表示。表格中,以没有空格的字符串为索引。Kaldi 中,称从表格文件中读取的一个字符串为 rspecifier, 写入表格文件的一个字符串为wspecifier。有两种表格文件格式:archive 和 script 。其读取方式和非表格型数据类似,也是使用 rxfilename/wxfilename的格式,其格式为[options]:path:[offset]。需要在 options 中注明是archive文件还是script文件。例如
wav-to-duration scp:wav.scp ark,t:-

rspecifiers 和 wspecifier

rspecifiers 和 wspecifier 是 Kaldi 中定义的两种术语,是存在于表格文件中的文件名,格式类似于 rxfilename/wxfilename, 亦可以采用管道命令呢,对于ark文件,可以添加选项,如ark,s,cs:-等。

两种存储格式:script-file 和 archive-file

script-file 是一种文件指针,里面包含的是具体文件所在路径,其格式为
key rspecifier|wspecifier
key 是字段名字,rspecifier/wspecifier则是文件路径,rspecifier/wspecifier 可以有空格。例如 “utt1 gunzip -c /foo/bar/utt1.mat.gz”。
archive-file 里面装的是实际的数据。其文件格式是
key1 object1 key2 object2 key3 object3 …
ark文件可以连接在一起,依然是一个有效的ark文件。但是如果需要的文件时有序的话,连接的时候应该要注意。
ark文件和scp文件有可能同时写,这时可以这么写:ark,scp:foo.ark,foo.scp。

二、代码级 I/O 机制

典型的读写过程如下:

{ // input.  bool binary_in;  Input ki(some_rxfilename, &binary_in);  my_object.Read(ki.Stream(), binary_in);  // you can have more than one object in a file:  my_other_object.Read(ki.Stream(), binary_in);}// output.  note, "binary" is probably a command-line option.{  Output ko(some_wxfilename, binary);  my_object.Write(ko.Stream(), binary); }

花括号是用来限制输入输出对象的作用域的,这样可以自动析构输入输出对象,自动关闭文件。my_object是需要从文件读取到内存的对象。

文件读写

文件的读写采用 Input/Output 类
Input 类的接口有:
- 构造函数:Input (const std::string &rxfilename, bool *contents_binary=NULL) 和 Input ()
- bool Open (const std::string &rxfilename, bool *contents_binary=NULL)
- bool OpenTextMode (const std::string &rxfilename)
- bool IsOpen ()
- int32 Close ()
- std::istream & Stream ()
- 析构函数~Input ()
可以显式地调用Open()或Close() 来打开或关闭文件。Stream () 来读取文件流。
类似地,Output 类的接口有:
- 构造函数 Output (const std::string &filename, bool binary, bool write_header=true)和Output ()
- bool Open (const std::string &wxfilename, bool binary, bool write_header)
- bool IsOpen ()
- std::ostream & Stream ()
- bool Close ()
- ~Output ()

对象(Object)读写

Kaldi 的类中一般都有读写借口,其形式为

class SomeKaldiClass { public:   void Read(std::istream &is, bool binary);   void Write(std::ostream &os, bool binary) const;};

一般会通过摸板函数ReadKaldiObject(const std::string &filename, C *c) 和 WriteKaldiObject(const C &c, const std::string &filename, bool binary) 来调用某个类的Read() 和 Write ()。

表格(Table)读写

对于表格文件(scp,ark),Kaldi 定义了专门的表格读写器。表格读写器是关于 Holder 类的模板类。Holder 是一些辅助类,负责告诉表格读写器,对象的类型是什么,应该如何读写。表格读写器有四个类:
- RandomAccessTableReader
- SequentialTableReader
- TableWriter
- RandomAccessTableReaderMapped
这里面,Sequential 表示序列化访问,即支持使用迭代器遍历; Random 表示随机访问某几个。
读写的时候,需要提供 wspecifiers/rspecifiers 给读写器。
例子:

std::string feature_rspecifier = "scp:/tmp/my_orig_features.scp",   transform_rspecifier = "ark:/tmp/transforms.ark",   feature_wspecifier = "ark,t:/tmp/new_features.ark";// there are actually more convenient typedefs for the types below,// e.g. BaseFloatMatrixWriter, SequentialBaseFloatMatrixReader, etc.TableWriter<BaseFloatMatrixHolder> feature_writer(feature_wspecifier);SequentialTableReader<BaseFloatMatrixHolder> feature_reader(feature_rspecifier);RandomAccessTableReader<BaseFloatMatrixHolder> transform_reader(transform_rspecifier);for(; !feature_reader.Done(); feature_reader.Next()) {   std::string utt = feature_reader.Key();   if(transform_reader.HasKey(utt)) {      Matrix<BaseFloat> new_feats(feature_reader.Value());      ApplyFmllrTransform(new_feats, transform_reader.Value(utt));      feature_writer.Write(utt, new_feats);   }}

对于表格,Kaldi 将其看为一系列名值对。一般除了前面介绍的Open() 等成员函数外,还有这几个函数:
- 对 RandomAccessTableReader 类:
const T &Value(const std::string &key) 返回对应于 key 的键值的引用。
bool HasKey(const std::string &key) 表明是否有这个key。
- 对 SequentialTableReader 类:
inline std::string Key() 返回对当前键 key 的引用
const T &Value() 返回对应于当前键 key 的键值的引用
void Next() 迭代到下一个键。
inline bool Done() 表明是否完成迭代。
void FreeCurrent() 在使用大对象时,用来节约内存。
- 对 SequentialTableReader 类:
inline void Write(const std::string &key, const T &value) 表示写入键值对。
void Flush() 为刷新缓冲区。
- 对 RandomAccessTableReaderMapped 类:
这个类是用来随机访问每一个句子对应一个key,有很多相同的说话人的情况。如rxfilename文件中有这样的行
utt1 spk1
utt2 spk1
utt3 spk1
它在使用时,除了需要提供rxfilename, 还需要提供utt2spk的映射表
RandomAccessTableReaderMapped(const std::string table_rxfilename, const std::string &utt2spk_rxfilename)

附录

1.rspecifier 的选项

Options Meanings “o” 断言每一个条目只读取一次 “p” 无视数据错误,继续下一条 “s” key按照排序后的字符串读取 “cs” 要求key排序 “no” “o”的反义词 “np” “p”的反义词 “ns” “s”的反义词 “ncs” “cs”的反义词 “b” 无作用 “t” 无作用

有关代码文件:
kaldi-io.h

参考文献:
Kaldi文档:http://kaldi-asr.org/doc/io.html

0 2
原创粉丝点击