OfficialKaldi(十三)| Kaldi的I/O机制(翻译注解)

Kaldi I/O 机制

 
本页概述了Kaldi的输入输出机制。 本文档的这一部分面向I/O的代码级机制;有关更多面向命令行的文档,请参阅Kaldi I/O from a command-line perspective.
kaldi中的类的IO接口
Kaldi中定义的类具有统一的I/O接口。标准接口如下所示:
class SomeKaldiClass { public: void Read(std::istream &is, bool binary); void Write(std::ostream &os, bool binary) const; }; 12345
 
注意这些函数的返回值都是void。执行中的错误通过exception来指出(参考Kaldi logging and error-reporting。参数“binary”指定读写的对象是否按照二进制文件读写或者普通的text文件读写。调用读写函数的代码必须知道要进行读写的对象是按照哪种方式进行读写的(参考How Kaldi objects are stored in files查看这些代码是怎么知道这些对象是以什么形式存储的)。请注意,这个“binary”变量的形式不一定与windows上以“binary”或者“text”模式打开文件的形式相同(参看How the binary/text mode relates to the file open mode)。Read和Write函数可能有其他可选参数。一个例子:
class SomeKaldiClass { public: void Read(std::istream &is, bool binary, bool add = false); }; 1234
 
如果add == true,则Read函数会将磁盘上的内容(例如统计信息)添加到当前类的内容中(如果该类当前不为空)。
基本类型和STL类型的输入输出
有关此功能所涉及的功能列表,请参见“低级I/O功能”。kaldi提供了这些函数,使其更容易读写基本类型;它们主要来自Kaldi类的读写功能。 Kaldi类没有必要使用这些函数,它们只需要确保自己的Read函数可以读取自己的Write函数产生的数据即可。 此类别中最重要的函数是ReadBasicType()和WriteBasicType();这些是涵盖bool,float,double和integer类型的模板。在Read和Write函数中使用它们的一个例子如下:
// we suppose that class_member_ is of type int32. void SomeKaldiClass::Read(std::istream &is, bool binary) { ReadBasicType(is, binary, &class_member_); } void SomeKaldiClass::Write(std::ostream &os, bool binary) const { WriteBasicType(os, binary, class_member_); } 1234567
 
我们假设class_member_的类型为int32,这是一种已知大小的类型。在这些函数中使用int这样的类型并不安全。在二进制模式下,这些函数实际上会编入一个字符来编码整数类型的大小和符号,如果读取时不匹配则将失败。kaldi没有自动转换它们。目前,必须在I/O中使用kaldi中已知大小的整数类型(建议将int32用于"通用"类型)。另一方面,浮点类型会自动转换。这是为了便于调试,因此您可以使用-DKALDI_DOUBLE_PRECISION进行编译,并仍然可以读取没有该选项的二进制文件。我们的I / O例程没有字节交换;如果这对您来说是个问题,请使用文本格式。 还有WriteIntegerVector()和ReadIntegerVector()模板化函数。它们与WriteBasicType()和ReadBasicType()函数具有相同的样式,但适用于std :: vector ,其中I表示整数类型(同样,它的大小应该在编译时知道,例如int32)。其他一些重要的低级I / O函数比如:
void ReadToken(std::istream &is, bool binary, std::string *token); void WriteToken(std::ostream &os, bool binary, const std::string & token); 12
 
token必须不为空,且不能有空格,通常来讲在实践中是一个看起来像XML的字符串,如“”或“”或“”。这些功能可以完成它们的功能。为方便起见,我们还提供了ExpectToken(),它类似于ReadToken(),但你给传入预期的字符串(如果它没有得到,会抛出异常)。调用这些代码的示例如下:
// in writing code: WriteToken(os, binary, "<MyClassName>"); // in reading code: ExpectToken(is, binary, "<MyClassName>"); // or, if a class has multiple forms: std::string token; ReadToken(is, binary, &token); if(token == "<OptionA>") { ... } else if(token == "<OptionB>") { ... } ... 12345678910
 
还有WritePretty()和ExpectPretty()函数。它们的使用频率较低,它们的行为与相应的Token函数相似,只是它们实际上只在文本模式下读写,并且它们接受任意字符串(即它们允许空格); ReadPretty函数还接受预期不同的有空格的输入。 Kaldi类中的Read函数永远不会检查文件的结尾,而是会读到Write函数写入的结尾之前(在文本模式下,留下一些未读的空白并不重要)。这样可以将多个Kaldi对象放在同一个文件中,并且还允许归档概念(请参阅Kaldi归档格式)。
kaidi对象是如何存储在文件中的
上面说过,Kaldi读取代码需要知道它是以文本还是二进制模式读取,并且kaldi不希望用户必须跟踪给定文件是文本还是二进制。因此,包含Kaldi对象的文件需要声明它们是否包含二进制或文本数据。二进制Kaldi文件将以字符串“\0B”开头;由于文本文件不能包含“\0”,因此它们不需要标志头。如果您使用标准C++机制打开文件(但是通常不要这样做,请参阅如何下一节在Kaldi中打开文件),在执行任何操作之前,您必须先处理标志头。您可以使用函数InitKaldiOutputStream()(这也会设置流精度)和InitKaldiInputStream()来完成此操作。
如何在kaldi中打开文件
假设想要从/向磁盘加载或保存Kaldi对象,并假设它是类似于语音模型这样(数量只有一个)的对象,(不是数目很多的对象,比如语音特征文件的对象,这种大量数据文件的打开方式请参阅kaldi的表格文件概念。通常kaldi用户需要使用Input和Output类。一个例子是:
{ // 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); } 123456789101112
 
括号里面的代码目的是让Input和Output对象在读写完成后立即"超出范围"从而可以马上关闭被读写的文件。这么做似乎看起来意义不大(为什么不使用标准c++的stream呢?)这么做的目的是一来可以支持更多扩展类型的文件名,而且它还使处理错误更容易一些(输入和输出类将打印信息性错误消息并在出错时抛出异常)。请注意,文件名中包含“rxfilename”和“wxfilename”。我们经常使用这样的名称,它们是用来提醒编程人员这些是kaldi的扩展文件名。我们将在下一节中描述rxfile和wxfile这类实体。 Input类和Output类的接口比上面的示例更丰富一点,你也可以调用Open()来打开文件或者使用Close()来关闭文件而不是让文件超出读写范围。这些函数返回布尔状态值,而不是像构造函数和析构函数那样抛出异常。当然也可以调用Open()函数(和构造函数)来读写非kaldi的文件,这样他们不会处理kaldi二进制header。当然你可能不需要任何这些额外的功能。 查看流式打开类和下一节的rxfilenames和wxfilenames获取更多Input类和Output类的信息。
扩展文件名rxfilenames和wxfilenames
rxfilenames和wxfilenames并不是类,他们是一系列的以各种类型名称出现的输入和输出描述符,包含着以下含义:
  • 一个rxfilenames,是一个字符串,表示由Input类进行解析的扩展文件名称,代表读
  • 一个wxfilenames,也是一个字符传,表示由Ouput类进行解析的扩展文件名称,代表写
  • rxfilenames的类型主要包含以下几种:
  • "-"或者”“表示标准输入(standard input)
  • ”some command |"表示管道输入。比如可以先执行some command然后将剩下的字符串通过popen传给shell(再交由你的kaldi程序处理),(我的理解是比如我自己写一个程序,传参时使用了Input类获取一个ls -l | 的字符串,会先执行ls -l ,将ls -l的结果传到Input类)
  • “/some/filename:12345"表示的是访问文件的偏移量。打开文件并且seek到12345的偏移位置。
  • "/some/filename"非以上类型的string,都认为是普通文件(当然打开他们之前会先检查一些明显的错误)
  • 可以调用ClassifyRxfilename()来查看rxfilename的具体类型,但是这么做不是特别有必要。
wxfilename的类型包含以下几种:
  • "-“或者”"表示标准I/O
  • “| some command” 同样表示管道
  • “/some/filename” 普通文件
  • 同样可以使用ClassifyWxfilename()查看wxfilename的具体类型。
Table(表格文件) 概念
表格是指的kaldi的一种概念,并不是指的一个c++类型。它由一些已知类型的对象集合组成,由字符串索引,这些字符串必须是一个token(所谓token是指非空的、不含空白分隔符的字符串),表格文件的典型示例如:
  • 特征文件集合feature files (由 Matrix表示) ,由utt id索引,utt id指的是语音文件编号
  • 音标集合 transcriptions (由 std::vector表示), utt id索引
  • 约束MLLR变换 (由 as Matrix表示), speaker id 索引
  • 用表格格式写入的类型
  • 中有更多关于如何处理这些类型的表格的细节信息。这里我们只解释一般原则和内部机制。表格可以以两种可能的格式存在于磁盘上(或实际上存在于管道中):脚本文件或存档(参见下文,Kaldi脚本文件格式和Kaldi存档格式)。有关与表相关的类和类型的列表,请参阅“
  • 表类型和相关函数
  • ”。可以通过三种方式访问​​表:使用TableWriter,SequentialTableReader和RandomAccessTableReader(还有RandomAccessTableReaderMapped,这是我们稍后将介绍的特殊情况)。这些都是模板; 它们不是在表中的对象上进行模板化,而是在Holder类型上(参见下面,Holders作为Table类的辅助类),告诉Table相关代码如何读取和写入该类型的对象。要打开表类型,必须提供一个名为wspecifier或rspecifier的字符串(请参阅下面的“ 指定表格格式:wspecifiers和rspecifiers”))告诉Table 相关代码,这个表示如何存储在磁盘上的,并为其提供各种其他指令。我们用一些示例代码来说明这一点。此代码读取特征,将他们做线性转换并将其写入磁盘。
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); } } 12345678910111213141516
 
这种设置的好处是访问表的代码可以将它们视为通用映射或列表。读取过程的数据格式和其他方面(例如,其容错率error tolerance)可以通过rspecifiers和wspecifiers中的选项来控制,而不必由调用代码处理; 在上面的例子中,选项“,t”告诉它以文本形式写入数据。 表格的理想情况可能是从字符串到对象的映射。但是,只要我们不对特定表进行随机访问,如果代码包含特定字符串的重复条目(即对于写入和顺序访问,它的行为更像是元素为pair的列表),代码也可以正常运行。 有关要读取和写入特定类型的Table类型的typedef列表,请参阅“特定表类型”。
Kaldi的script-file格式(脚本格式,scp)
脚本文件(这个名字可能略有误导)是一个文本文件,其中每行通常包含以下内容:
some_string_identifier /some/filename 1
 
脚本文件中的另一种有效行是:
utt_id_01002 gunzip -c /usr/data/file_010001.wav.gz | 1
 
总之,通用格式是
<key> <rxfilename> 1
 
脚本文件中的范围(用于获取矩阵的子部分)
我们还允许在rxfilename之后出现一个可选的“范围说明符”; 这对于表示矩阵的部分(例如行范围)很有用。目前只有矩阵类型的数据支持。例如,我们可以表示矩阵的行范围如下:
utt_id_01002 foo.ark:89142[0:51] 1
 
这意味着矩阵的行0到51(包括)。行和列范围都可以表示,例如
utt_id_01002 foo.ark:89142[0:51,89:100] 1
 
如果您只想表示列范围,可以将行范围留空,如下所示:
utt_id_01002 foo.ark:89142 [,89:100] 1
 
Kaldi如何处理scp file的每一行
当读取一行脚本文件时,Kaldi将修剪行前和行后的空白字符,然后在第一个“空格”上分割该行。第一部分成为表中的键(例如,utt id,在本例中为“utt_id_01001”),第二部分(在剥离可选范围说明符之后)成为xfilename(指的是wxfilename或rxfilename,在这种情况下“gunzip -c /usr/data/file_010001.wav.gz |”)。不允许使用空行或空xfilename。脚本文件可能对读取或写入有效,或两者兼有,具体取决于xfilenames是有效的rxfilenames还是wxfilenames,或两者兼而有之。
注意:一旦删除了可选范围,scp file行上出现的(r,x)filenames通常可以像给出文件名一样给予任何Kaldi程序。包含字节偏移的rspecifiers也是如此,例如foo.ark:8432。字节偏移将指向对象数据的开头(而不是指向archive文件中,数据之前的key-value)。对于二进制数据,字节偏移指向对象之前的“\0B”; 这允许读取代码在读取对象之前确定数据是二进制的。
Kaldi archive 格式(归档格式,ark)
Kaldi归档格式非常简单。首先回想一下,token被定义为无空白字符串。存档格式可以描述为:
token1 [something]token2 [something]token3 [something] .... 1
 
我们可以将其描述为零或多次重复:(一个token;然后是一个空格字符;然后是调用Holder的Write函数的结果)。回想一下,Holder是一个告诉Table代码如何读取或写入内容的对象。 在编写Kaldi对象时,Holder编写的[something]将构成二进制模式头(如果是二进制文件),然后是调用对象的Write函数写入结果。在编写简单的非Kaldi对象(如int32或float或vector )时,我们编写的Holder类通常确保在文本格式中,[something]是一个以换行符结尾的字符串。这样,存档有一个很好的一行一行格式,看起来像一个脚本文件,例如:
utt_id_1 5 utt_id_2 7 ... 123
 
是我们用于存储整数的文本存档格式。 ark格式使您可以将ark文件连接在一起,它们仍然是有效的ark(假设它们具有相同类型的对象)。该格式设计为pipe-friendly,即您可以将ark放在管道中,读取它的程序不必等到管道结束才能处理数据。为了高效随机访问ark,可以同时将ark与包含存档偏移的scp一起写入磁盘。为此,请参阅下一节。
指定表格式:wspecifiers和rspecifiers
Table类需要一个传递给构造函数或Open方法的字符串。如果传递给TableWriter类,则此字符串称为wspecifier;如果传递给RandomAccessTableReader或SequentialTableReader类,则称为rspecifier 。有效的rspecifiers和wspecifiers的示例包括:
std::string rspecifier1 = "scp:data/train.scp"; // script file. std::string rspecifier2 = "ark:-"; // archive read from stdin. // write to a gzipped text archive. std::string wspecifier1 = "ark,t:| gzip -c > /some/dir/foo.ark.gz"; std::string wspecifier2 = "ark,scp:data/my.ark,data/my.scp"; 12345
 
通常,rspecifier或wspecifier由逗号分隔的、无序的一个或两个字母的选项列表和一个字符串“ark”和“scp”组成,后跟一个冒号,再跟一个rxfilename或wxfilename。冒号前的选项顺序无关紧要。
同时编写存档和脚本文件
wspecifiers有一个特殊情况:它们可以在冒号之前“ark,scp”,冒号之后是用于编写ark文件的wxfilename,然后是逗号,然后是用于scp文件的wxfilename。例如,
"ark,scp:/some/dir/foo.ark,/some/dir/foo.scp" 1
 
这将编写一个ark文件,以及一个scp文件,其中包含“utt_id /somedir/foo.ark:1234”等行,用于指定存档中的偏移量,以实现更高效的随机访问。然后,您可以使用scp文件执行任何您喜欢的操作,包括将其分解为段,并且它将像任何其他scp文件一样运行。请注意,虽然冒号前的选项顺序通常不重要,但在这种特殊情况下,“ark”必须位于“scp”之前; 这是为了防止在冒号之后混淆两个wxfilenames的顺序(ark始终是第一个)。指定存档的wxfilename应该是普通文件名,否则写入的scp文件将无法由Kaldi直接读取,但代码不会强制执行此操作。
wspecifiers的有效选项
允许的wspecifier选项是:
  • “b”(二进制)表示以二进制模式写入(当前不需要,因为它始终是默认值)。
  • “t”(文本)表示以文本模式写入。
  • “f”(刷新)表示在每次写入操作后刷新流。
  • “nf”(无刷新)表示在每次写入操作后不刷新流(当前是无意义的,但调用代码可以更改默认值)。
  • “p”表示静默模式,它影响“scp:”wspecifiers,其中scp文件缺少某些条目:“p”选项将导致它静默地不为这些文件写任何内容,并且不报告任何错误。
  • 例子:
"ark,t,f:data/my.ark" "ark,scp,t,f:data/my.ark,|gzip -c > data/my.scp.gz" 12
 
rspecifiers的有效选项
在阅读下面的选项时,请记住,如果ark实际上是一个管道(并且经常是管道),那么读取ark的代码永远不会在ark中寻找。如果RandomAccessTableReader正在读取ark,则读取代码可能必须在内存中存储许多对象,以防以后再次请求它们;或者就需要在ark文件中从头到尾的去查找key,即使这个key并不存在于ark文件中。下面的一些选项代表了防止这种情况的方法。 重要的rspecifier选项是:
  • “o”(一次)是用户断言RandomAccessTableReader代码的方式,每个密钥只会被查询一次。这使得它不必在内存中保留已读取的对象,以防再次需要它们。
  • “p”(静默)指示代码忽略错误并仅提供它可以提供的数据; 无效数据被视为不存在。在scp文件中,这意味着对HasKey()的查询会强制加载相应的文件,因此如果文件损坏,代码可以知道返回false。在ark中,如果ark已损坏(将停止读取),此选项将忽略引发异常。
  • “s”(已排序)指示正在读取的存档中的键按排序字符串顺序排列的代码。对于RandomAccessTableReader,这意味着当为某个不在存档中的key调用HasKey()时,它会在遇到“更高”键时立即返回false而不必读到最后。
  • “cs”(called-sorted)指示对HasKey()和Value()的调用将按字符串顺序排列。因此,如果为某些字符串调用这些函数之一,则读取代码可以丢弃较低编号的键的对象。这节省了内存。实际上,“cs”表示用户断言程序可能正在迭代的ark文件本身已经排序。
典型的rspecifiers的例子:
"ark:o,s,cs:-" "scp,p:data/my.scp" 12
 
Holders作为Table 类的助手
如前所述,Table类,即TableWriter,RandomAccessTableReader和SequentialTableReader,都是在Holder类上进行模板化的。Holder不是实际的类或基类,而是描述了一系列类,并且这些类的名称以Holder结尾,例如TokenHolder或KaldiObjectHolder。(KaldiObjectHolder是一个通用的Holder,可以在满足Kaldi类的输入/输出样式中描述的Kaldi I / O样式的任何类中进行模板化)。我们编写了模板类GenericHolder以便记录Holder类必须满足的属性,但一般不使用GenericHolder。
Holder类“held”的类型是typedef Holder :: T(其中Holder是所讨论的实际Holder类的名称)。可在“Holder types”中找到可用持有人类型的列表。
二进制/文本模式如何与文件的打开模式相关
本节仅适用于Windows平台。一般规则是,在写入时,文件模式将始终匹配Write函数的“binary”参数; 在读取二进制数据时,文件模式将始终为二进制,但在读取文本数据时,文件模式可能是二进制或文本(因此文本模式读取功能必须始终接受Windows插入的额外“\ r”字符)。这是因为我们并不总会知道,其内容是二进制还是文本直到我们打开文件,所以当不确定时,我们以二进制模式打开。
在随机访问模式下读取ark文件时避免内存占用过多
当Table代码以随机访问模式读取大型ark文件时,存在内存占用过多的可能性。只要使用RandomAccessTableReader 类型的对象读入ark,就可能发生这种情况。编写Table 代码是为了首先确保正确性,所以当以随机访问模式读取ark时,除非你给Table的读取代码一些额外的信息(我们将在下面讨论),它永远不会丢弃它拥有的任何对象阅读,以防你再次请求它。这里一个显而易见的问题是:为什么Table 代码不track到每个对象在文件中的开始位置,并在需要时fseek()到该位置?我们还没有实现这个,原因如下:你可以fseek()的唯一情况是当读取的ark是一个实际文件时(即不是管道命令或标准输入)。如果ark是磁盘上的实际文件,您可以将附加的scp文件写入文件中(使用“ark,scp:”前缀,请参阅同时前面的部分),然后将该scp文件提供给需要读取存档的程序。这几乎与直接读取ark一样节省时间,因为读取scp文件的代码足够智能,可以避免在不需要时重新打开文件并不必要地调用fseek()。因此,将ark视为特殊情况并将偏移缓存到文件中并不能解决任何问题。 当您以随机访问模式读取ark时,可能会发生两个不同的问题; 如果你只使用“ark:”前缀而没有其他选项,这些都会发生。
  • 如果您查找的key不在ark中,则读取代码将被一直读到ark文件结束,以确保它不存在。
  • 每次代码读取一个对象时,它都会被强制保存在内存中,以防您以后请求它。
  • 关于第一个问题(必须读到文件结尾),你可以避免这种情况的方法是断言存档按key排序(使用“C”使用的正常字符串排序顺序,并且程序“sort” 如果设置“ export LC_ALL = C”)。您可以在读取ark时使用“s”选项执行此操作:例如,rspecifier “ark,s: - ”指示代码将标准输入读取为ark,并期望它按排序顺序。Table 代码检查您断言的内容实际上是否为真,如果没有则会崩溃。当然,您必须以这样的方式设置脚本,即ark实际上是按键排序的(通常这将在初始特征提取阶段完成),这也是为什么kaldi里面的很多脚本执行完了之后会有fix_data_dir.sh这一步。
关于第二个问题(在以后需要的情况下被迫将object保存在内存中),有两种解决方案。
  • 第一个解决方案是一个相当脆弱的解决方案,即提供“o”选项; 例如,rspecifier “ark,o:- ”从标准输入读入并声明您只需要询问每个对象一次。为了能够断言这一点你必须知道有关程序如何工作,且必须知道提供给程序的其他一些Table不包含任何重复的键(是的,表可以有重复的键,只要它们以顺序模式访问)。
如果提供“o”选项,则Table可以在访问对象后释放它们。但是,只有当您的ark文件完美同步且没有间隙或缺少元素时,这才有效。例如,假设您执行以下命令:
some-program ark:somedir/some.ark "ark,o:some command|" 1
 
程序“some-program”将首先在存档“somedir / some.ark”上顺序迭代,然后对于它遇到的每个key,通过随机访问访问第二个ark。请注意,命令行参数的顺序不是任意的:我们已经尝试采用这样的约定:将按顺序访问的rspecifiers出现在将通过随机访问访问的那些之前。
假设两个档案大多是同步的,但可能有间隙(即缺少key,例如由于特征提取失败,数据对齐等)。只要第一个ark中存在间隙,程序就必须从第二个ark中缓存关联的对象,因为它不知道以后不会调用它(它只能在你有一个对象访问过后才释放它))。第二个存档中的间隙会更严重,因为如果存在一个元素的间隙,当程序要求该键时,它必须一直读到第二个ark的末尾以查找它,并且必须缓存沿途的所有对象。
  • 第二种解决方案更强大,就是使用“called-sorted”(cs)选项。这断言程序将按排序顺序请求对象,当然这也需要知道程序如何工作,以及任何顺序访问的ark都是有序的。“cs”选项通常与“s”选项一起使用最为有用。假设我们执行以下命令:
some-program ark:somedir/some.ark "ark,s,cs:some command|" 1
 
我们假设两个ark都按顺序排序好,并且程序对第一个归档进行顺序访问,对第二个归档进行随机访问。现在,这种方式对于档案中的“间隙”处理会更有效。首先想象第一个档案中存在间隙(例如,其key是001,002,003,081,082 …)。当在key 003之后立即搜索第二个存档key 081时,读取第二个ark的代码可能遇到键004,005等,但它可以丢弃关联的对象,因为它知道在081之前不会有任何key被访问(感谢“cs”选项)。如果第二个存档中存在间隙,则可以使用第二个ark已经排序的事实,以避免搜索直到文件末尾(这是“s”选项的功劳)。
io_sec_mapped
为了压缩在许多程序中重复出现的特定代码模式,我们引入了模板类型RandomAccessTableReaderMapped。与RandomAccessTableReader不同,这需要两个初始化参数,例如:
std::string rspecifier, utt2spk_map_rspecifier; // get these from somewhere. RandomAccessTableReaderMapped<BaseFloatMatrixHolder> transform_reader(rspecifier, utt2spk_map_rspecifier); 123
 
如果utt2spk_map_rspecifier是空字符串,则其行为就像常规的RandomAccessTableReader一样。如果它是非空的,例如ark:data/train/utt2spk,它将从该位置读取utt 到 speaker(说话人) 的映射,并且每当查询特定字符串(例如utt1)时,它将使用该映射将话语id转换为一个 speaker-id(例如spk1)并使用它作为查询从rspecifier读取的Table的键。utterance-to-speaker的map也是一个ark,因为使用Table相关的代码是读取这种map最简单的方式。
个人总结:
scp文件不包含具体的数据(或者对象),只包含数据对应的路径 ark文件则包括具体的数据(或者对象)

posted on 2020-12-11 10:13  AI大道理  阅读(253)  评论(0编辑  收藏  举报

导航