好记性不如烂笔头

Kaldi-数据准备

Posted on By ZealerDrm

如需转载本网站内容,请标明转载来源,且保持作品完整性

数据准备

子目录local/下的脚本都是和数据集相关的。

数据准备阶段的输出包含两部分。一部分与“数据”相关 (保存在诸如data/train之类的目录下),另一部分则与“语言”相关 (保存在如data/lang/之类的目录下)。“数据”部分与数据集 的录音相关,而“语言”部分与语言本身更相关的内容,例如音频字典、音素集合 以及其他Kaldi需要的关于音素的额外信息。 如果你想用已有的识别系统和语言模型对你的数据进行解码。

数据部分

在egs/swbd/s5下ls data/train得到

cmvn.scp feats.scp reco2file_and_channel segments spk2utt text utt2spk wav.sc

不是所有文件都同等重要,如果要设置简单点,分段(segmentation)信息是不必要的 (即一个文件里只有一段音频),你只需要自己创建“utt2spk”、“text”、“wav.scp”, “segments”和““reco2file_and_channel”是可选的,根据实际需要决定是否创建。剩下的就交给标准脚本。

下面来描述一下这些文件,首先从那些需要手动创建的文件开始。

需要手动创建的文件

text文件

head -3 data/train/text

sw02001-A_000098-001156 HI UM YEAH I'D LIKE TO TALK ABOUT HOW YOU DRESS FOR WORK AND
sw02001-A_001980-002131 UM-HUM
sw02001-A_002736-002893 AND IS	

每行的第一项是音频编号(utterance-id),可以是任意的文本字符串, 但是如果其中还包括了说话人信息,就把说话人编号(speaker-id)作为音频编号的前缀。例如: “sw02001-A_000098-001156”,这对音频文件的排序非常重要。音频编号后面是每段音频的标注。

注意:尽管在这个特别的例子中,我们用下划线分割了音频编号和说话人部分,但是通常会用“-”,这样更安全,因为它的ASCII值更小。

wav.scp

head -3 data/train/wav.scp

sw02001-A /home/dpovey/kaldi-trunk/tools/sph2pipe_v2.5/sph2pipe -f wav -p -c 1 /export/corpora3/LDC/LDC97S62/swb1/sw02001.sph |
sw02001-B /home/dpovey/kaldi-trunk/tools/sph2pipe_v2.5/sph2pipe -f wav -p -c 2 /export/corpora3/LDC/LDC97S62/swb1/sw02001.sph |

格式为:

<recording-id><extended-filename>

其中,“extended-filename”可能是一个实际的文件名,或者像本例子中那样,是一段提取wav格式文件的命令。 extended-filename末尾的“|”符号表明,整个命令应该被解释为一个pipe。recording-id 待会儿解释。 注意:如果“segments”文件不存在,wav.scp每一行的第一项就是音频编号(utterance-id)。

在Switchboard中,有“segments”文件,所以下面我们就讨论一下这个文件。

segment

这个文件格式为:

<utterance-id> <recording-id> <segment-begin> <segment-end>

其中,segment-begin和segment-end以秒为单位。它们指明了一段发音在 一段录音中的时间偏移量。“recording-id”和在wav.scp中使用的是同一个标识符。 这是一个任意的标识符,可以随意指定。

reco2file_and_channel

这个文件只是在使用NIST的sclite工具对结果进行评分时使用。

head -3 data/train/reco2file_and_channel

sw02001-A sw02001 A
sw02001-B sw02001 B
sw02005-A sw02005 A

格式为:

<recording-id> <filename> <recording-side (A or B)>

filename通常时.sph文件的名字,需要去掉sph这个后缀,但也可以时任何其他在 “stm”文件中使用的标识符。“录音方”则是一个电话对话中两方通话(A或B)的概念。 如果不是两方通话,那么保险起见最好使用A。如果没有stm文件,或者根本不知道这是个啥玩意, 那么可能就不需要reco2file_and_channel文件。

utt2spk

这个文件指明某一段音频是哪个人发出的。

head -3 data/train/utt2spk 

sw02001-A_000098-0011562001-A 
sw02001-A_001980-0021312001-A 
sw02001-A_002736-0028932001-A

格式为:

<utterance-id><speaker-id>

注意说话人编号不需要与说话人实际的名字准确的对应起来,只需要大概能够猜出来即可, 在这种情况下,我们假定每个说话方对应一个说话人,但当某个说话人把电话交给另个人时, 对应就可能不完全正确了。这都没问题的。如果完全没有关于说话人的信息,可以把音频编号当作说话人, 那么此时格式就变为:

<utterance-id> <utterance-id>

在一些示例脚本中还可能有另外一个文件,它在Kaldi识别系统的建立过程中只是偶尔使用。 在ResourceManagemen(rm)设置中,该文件是这样的:

head -3 ../../rm/s5/data/train/spk2gender
adg0 f
ahh0 m
ajp0 m

这个文件根据说话人的性别,将每个说话人编号映射为“m”或“f”。

上述所有文件都需要被排序。如果没有排序,你在运行脚本时就会出现错误。这与Kaldi I/O框架有关, 归根到底是因为排序后的文件可以在一些不支持fseek()的串中进行随机访问查找,例如一条pipe命令。 Many Kaldi programs are reading multiple pipes from other Kaldi commands, reading different types of object, and are doing something roughly comparable to merge-sort on the different inputs; merge-sort, of course, requires that the inputs be sorted. Be careful when you sort that you have the shell variable LC_ALL defined as “C”, 需要这样做:

export LC_ALL=C

如果不这样做,这些文件的排序方式会与C++排序方式不同,Kaldi就会崩溃。


10月6日续

不需要手动创建的文件

数据目录下的其他文件可以由前面提供的文件生成。可以用如下的一条命令创建“spk2utt” (这是一条从egs/rm/s5/local/rm_data_prep.sh中摘取的命令):

utils/utt2spk_to_spk2utt.pl data/train/utt2spk > data/train/spk2utt

这是因为utt2spk和spk2utt文件中包含的信息是一样的。spk2utt文件的格式是:

<speaker-id><utterance-id1><utterance-id2>...

feats.scp

head -1 data/train/feats.sc
sw02001-A_000098-001156 /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:24

这个文件指向已经提取好的特征——在这个例子中我们所使用的是MFCC。feats.scp文件的格式是:

<utterance-id><extended-filename-of-features>

每一个特征文件保存的都是Kaldi格式的矩阵。在这个例子中,矩阵的维度是13(即列数,行数 则和文件长度有关,标准情况下帧长20ms,帧移10ms,所以一行特征数据对应10ms的音频数据。 但在Kaldi中,实际返回的帧数可能比预想的要小下点。例如,音频是5.68s,返回的通常是565帧而不是568。 这和Kaldi中处理不足以分帧的剩余数据的方式有关,目前这种方式是兼容HTK的。需要一直向下传递window size和 frame shift的值,才能准确算出每一帧的timing。这种方式在实际使用中有不少问题。)

第一行的“extended filename”,即/home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:24, 意思是,打开存档(archive)文件/home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark,fseek()定位到24(字节),然后开始读数据。

feats.scp文件由如下命令创建:

steps/make_mfcc.sh --nj 20 --cmd "$train_cmd"data/trainexp/make_mfcc/train$mfccdir 

该句被顶层的“run.sh”脚本调用。命令中一些shell变量的定义,请查阅对应run.sh。 $mfccdir是.ark文件将被写入的目录,由用户自定义。

cmvn.scp

data/train下最后一个未提到的文件。该文件包含了倒谱均值和方差归一化的统计量, 以说话人编号为索引。每个统计量集合都是一个矩阵,在本例中是2乘以14维。查看cmvn.scp,

head -3 data/train/cmvn.scp 
2001-A/home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/cmvn_train.ark:7 
2001-B/home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/cmvn_train.ark:253 
2005-A/home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/cmvn_train.ark:499 

与feats.scp不同,这个scp文件是以说话人编号为索引,而不是音频编号。该文件由如下命令创建:

steps/compute_cmvn_stats.sh data/train exp/make_mfcc/train $mfccdir

因为数据准备阶段的错误会影响后续脚本的运行,所以有个脚本来判断数据目录的文件格式是否正确。 运行 如下的例子:

utils/validate_data_dir.sh data/train

下面这个命令可能也很有用:

utils/fix_data_dir.sh data/train

(该命令可以对任何数据目录使用,而不只是data/train)。该脚本会修复排序错误,并会删除某些缺少必要数据的任何语句。

“lang”目录

查看一下lang目录:

ls data/lang
L.fst L_disambig.fst oov.int oov.txt phones phones.txt topo words.txt

除data/lang,可能还有其他目录拥有相似的文件格式:例如有个目录被命名为“data/lang_test”,其中包含了和data/lang完全一样的信息,但是要多一个G.fst文件。该文件是一个 FST形式的语言模型:

ls data/lang_test”
G.fst L.fst L_disambig.fst oov.int oov.txt phones phones.txt topo words.txt

注意,lang_test/由拷贝lang/目录而来,并加入了G.fst。每个这样的目录都似乎只包含为数不多的几个文件。 但事实上不止如此,因为其中phones是一个目录而不是文件:

ls data/lang/phones
context_indep.csl disambig.txt nonsilence.txt roots.txt silence.txt context_indep.int  extra_questions.int optional_silence.csl sets.int word_boundary.int context_indep.txt extra_questions.txt optional_silence.int sets.txt word_boundary.txt disambig.csl nonsilence.csl optional_silence.txt silence.csl 

phones目录下有许多关于音素集的信息。同一类信息可能有三种不同的格式,分别以.csl、.int和.txt结尾。 幸运的是,作为一个Kaldi用户,没有必要去一一手动创建所有这些文件,因为有一个脚本“utils/prepare_lang.sh”能够根据更简单的输入为你创建所有这些文件。 在讲述该脚本和所谓更简单的输入之前,有必要先看下lang目录下到底有些什么内容。

“lang”目录下的内容

首先是有文件hones.txt和words.txt。这些都是符号表文件,符合OpenFst的格式定义。其中每一行首先是一个文本项,接着是一个数字项:

head -3 data/lang/phones.txt
<eps> 0
SIL 1
SIL_B 2
head -3 data/lang/words.txt
<eps> 0
!SIL 1
-'S 2

在Kaldi中,这些文件被用于在这些音素符号的文本形式和数字形式之间进行转换。

大多数情况下,只有utils/int2sym.pl、utils/sym2int.pl和OpenFst中的fstcompile和fstprint会读取这些文件。

L.fst、L_disambig.fst

文件L.fst是FST形式的发音字典,其中,输入的音素,输出的是词。文件L_disambig.fst也是发音字典, 但是还包含了为消除歧义而引入的符号,诸如#1、#2之类,以及为自身循环而引入的#0。#0能让消岐符号 “通过”整个语法。但不管是否能够明白,其实不用自己手动引入这些符号。

oov.txt

cat data/lang/oov.txt
<UNK>

在训练过程中,所有词汇表以外的词都会被映射为这个词(UNK即unknown)。

”本身并没有特赦的地方,也不一定非要用这个词。重要的是需要确保整个词发音只包含一个被指定为“垃圾音素”(garbage phone)的音素。该音素会与 各种口语噪声对齐。在这个特别设置中,该音素被称为,就是“spoken noise”的缩写:

grep -w UNK data/local/dict/lexicon.txt
<UNK>SPN

oov.int

文件oov.int包含的整数形式(从words.txt中提取的),在本设置中是221。在Resource Management设置中,oov.txt里有一个静音词, 在RM设置中被称为“!SIL”。在这种情况下,从词汇表中任意选一个词(放入oov.txt)——因为 训练集中没有oov词,所以选哪个都不能作用。

topo

topo内容如下:

cat data/lang/topo
<Topology>
<TopologyEntry>
<ForPhones>
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
</ForPhones>
<State> 0 <PdfClass> 0 <Transition> 0 0.75 <Transition> 1 0.25 </State>
<State> 1 <PdfClass> 1 <Transition> 1 0.75 <Transition> 2 0.25 </State>
<State> 2 <PdfClass> 2 <Transition> 2 0.75 <Transition> 3 0.25 </State>
<State> 3 </State>
</TopologyEntry>
<TopologyEntry>
<ForPhones>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
</ForPhones>
<State> 0 <PdfClass> 0 <Transition> 0 0.25 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 </State>
<State> 1 <PdfClass> 1 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State>
<State> 2 <PdfClass> 2 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State>
<State> 3 <PdfClass> 3 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State>
<State> 4 <PdfClass> 4 <Transition> 4 0.75 <Transition> 5 0.25 </State>
<State> 5 </State>
</TopologyEntry>
</Topology>

这个文件指明了所用HMM模型的拓扑结构。在这个例子中,一个“真正的”音素内含了3个发射状态,呈标准的三状态从左到右拓扑结构 ——即“Bakis”模型。(发射状态即能“发射”特征矢量的状态,与之对应的就是那些“假”的仅用于连接其他状态的非发射状态)。 音素1到20是各种静音和噪声。之所以会有这么多,是因为对词中不同位置的同一音素进行了区分(word-position=dependency)。这种情况下,实际上这里的静音和噪声音素大多数都用不上。不考虑在词 位话,应该只有5个代表静音和噪声的音素。所谓静音因素(silence phones)有更复杂的拓扑结构。每个静音音素 都有一个起始发射状态和一个结束发射状态,中间还有另外三个发射状态。不需要手动创建data/lang/topo。

phones

phones下有一系列的文件,指明了音素集合各种信息。这些文件大多数有三个不同版本:

一个“.txt”形式,如: head -3 data/lang/phones/context_indep.txt SIL SIL_B SIL_E

一个“.int”形式,如:

head -3 data/lang/phones/context_indep.int
1
2
3

一个“.csl”形式,内含冒号分割的列表:

cat data/lang/phones/context_indep.csl
1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20

三种形式的文件包含的是相同的信息,所以我们只关注人们更易阅读的“.txt”形式。文件 “context_indep.txt”包含一个音素列表,用于建立文本无关的模型。也就是说, 对这些音素不会建立需要参考左右音素的上下文决策树。实际上,建立的是更小的决策树, 只参考中心音素和HMM状态。这依赖于“roots.txt”。

文件context_indep.txt包含所有音素,包括那些所谓的“假”音素:例如静音(SIL),口语噪声(SPN),非口语噪声(NSN)和笑声(LAU):

cat data/lang/phones/context_indep.txt
SIL
SIL_B
SIL_E
SIL_I
SIL_S
SPN
SPN_B
SPN_E
SPN_I
SPN_S
NSN
NSN_B
NSN_E
NSN_I
NSN_S
LAU
LAU_B
LAU_E
LAU_I
LAU_S

因为考虑了词位,这些音素都有许多变体。不是所有的变体都会被实际使用。 这里,SIL代表静音词,会被插入到发音字典中(是一个词而不是一个词的一部分,可选的);SIL_B则代表一个静音 音素,应该出现在一个词的开端(这种情况应该永不出现);SIL_I代表词内 静音(也很少存在);SIL_E代表词末静音(不应该存在);而SIL_S则表示一种被视为 “单独词”的静音,意指这个音素只对应一个词——当你的发音字典中有“静音词”且标注中明确的 静音段时会有用。

silence.txt和onsilence.txt分别包含静音音素列表和非静音音素列表。 这两个集合时互斥的,且如果合并在一起,应该时音素的总集。 在本例中,silence.txt与context_indep.txt的内容完全一致。

head -3 data/lang/phones/silence.txt
SIL
SIL_B
SIL_E

head -3 data/lang/phones/nonsilence
IY_B
IY_E
IY_I

disambing.txt包含一个“消岐符号”列表

head -3 data/lang/phones/disambig.txt
#0
#1
#2

这些符号会出现在phone.txt中,被当作音素使用。

optional_silence.txt只含有一个音素。该音素可在需要的时候出现在词之间:

cat data/lang/phones/optional_silence.txt
SIL

(可选静音列表中)音素出现在词之间的机制是,在发音字典的FST中,可选择的让该音素出现在每个词 的词尾(以及每段发音的段首)。该音素必须在phones/中指明而不是仅仅出现在L.fst中。

语音识别评估标准

  1. WER-词错误率
  2. SER-句错误率(一句话中,一个词错了,句子就算是错误的。句子识别错误的的个数,除以总的句子个数即为SER)
  3. CER-字错误率

NIST是个数据库吗?