Skip to the content.

Kaldi是围绕隐马尔科夫模型的,“端到端”可以不需要隐马尔科夫模型,混合神经网络,双向长短记忆神经网络+CTC目标函数。

Y是输入音频,w是真正的词序列,求最大的P(w|Y)。由贝叶斯可得P(w|Y) = P(Y|w)P(w)/P(Y)。其中Y为确定值,于是P(Y)为常数,求极值时可以忽略。
对P(Y|w)和P(w)分开建模:
P(Y|w)在给定单词序列w时,可以得到特定音频信号Y的的概率,通常称为声学模型。
P(w)给定单词序列w的概率。通常称为语言模型。
Y通常每隔10毫秒在25毫秒的时间窗口中提取一个特征向量,输入概率模型的Y实际中通常是一系列特征向量的序列

wav长度1s,采样率16kHz :(1 * 1000) / 10 = 100帧,每帧有 (25 / 1000) * 16kHz = 400个样本
采样频率,也称为采样速度或者采样率,定义了每秒从连续信号中提取并组成离散信号的采样个数,它用赫兹(Hz)来表示。采样频率的倒数是采样周期或者叫作采样时间,它是采样之间的时间间隔。通俗的讲采样频率是指计算机每秒钟采集多少个信号样本。
Hz 每秒钟的周期性变动重复次数
feat/feature-window.h:106 int32 WindowSize() const {return static_cast(samp_freq (=16000 16Hz) * 0.001 * frame_length_ms(=10));} feat/feature-window.h:53 FrameExtractionOptions(): samp_freq(16000), frame_shift_ms(10.0), frame_length_ms(25.0), dither(1.0), preemph_coeff(0.97), remove_dc_offset(true), window_type("povey"), round_to_power_of_two(true), blackman_coeff(0.42), snip_edges(true), allow_downsample(false), allow_upsample(false), max_feature_vectors(-1)

单词序列w通常会拆成一个音素序列
Q是单词序列w对应的发音单元序列,这里简化为音素序列,那么声学模型p(Y|w)=∑p(Y|Q)P(Q|W),这个求和是单词序列w对应的所有可能得音素序列Q的集合计算边缘分布概率。于是声学模型就拆分成P(Y|Q)和P(Q|w)。特征向量|音素、音素|词序列
(第九页)P(Q|w):w中每个词发音为q的概率乘积。单词可能有多个发音,但通常多音词的发音不会很多,因此P(Q|w)比较容易从发音词典中计算出来。注:多音词不是带有多音字的词,多音词不同发音意义不同
P(Y|Q)是声学模型的核心,Q中每一个音素构建一个隐马尔科夫模型单元,根据Q的音素序列拼成一个句子级别的的隐马尔科夫模型,特征序列Y是隐马尔可夫模型的可观察输出。实际中要复杂一些,比如使用上下文三音子作为隐马尔科夫单元
P(w),用前n-1个词预测第n个词,n一般取3、4,已知w0…w(l-1),下一次是wl的概率

语音信号处理:

音频采样:采样记录电流值,两次采样的间隔称为采样周期,它的倒数是采样频率。例如每隔1/16000秒采样一次,采样频率为16000Hz。
量化:11页,样点格式转化
回声消除
噪声抑制:频域抑制、空域抑制
动态增益控制
音频编解码:主要用于网络传输
其中,采样和量化是声音信号转换为计算机数据必不可少的方法

发音和语言学:人类语音答题可分为有限的若干基本元素,称为音素
发音词典:表意单元与音素组合之间的映射。音素是人为定义概念,所以不同语言有不同的音素集,不同的发音词典。汉语常用拼音,由于多音字和多音词,发音词典并不一一映射。除语音音素,通常还要加入非语音音素如静音音素、噪音音素等。
实际中,还要考虑上下文影响造成的协同发音。喝多技术都是为了处理协同发音,例如:每个音素往往分为多个状态,分别对应不同的发音阶段,不同阶段的差异主要是受上下文的相邻音素影响。再比如,对每个音素建模时,建模对象实际上是由上下文音素组成的三音子(Triphone)组合。

语音识别评价:错误率(插入、删除、替换)、正确率、实时率(耗时÷句子时长)。错误率可能大于100%。

Kaldi集群

小型集群:  需要子任务进程对同一目录有读写权限,需要搭建网络文件系统(NFS),需要确保同一用户在不同机器上使用相同的UID和GID:id -u xxx和id -g xxx
完成NFS后,在要加入集群的机器上设置互相之间的免密登录,确认NFS的挂载点在所有机器上有相同的访问权限
大型集群:  Kaldi支持:SGE(Sun Grid Engine) 和 SLURM(Simple Linux Utility for Resource Management),默认用SGE,但书上推荐自己维护的读者使用SLURM。

多数示例默认用sge集群 queue.pl

单机 run.pl

配置了 NFS 和免密登录,可以使用 ssh.pl 进行任务分发,需要在训练环境目录创建 .queue 文件夹没在其中创建 machines 文件,将要使用的机器名写在里面:
$ cat .queue/machines
  a01
  a02
  a03

SLURM集群 slurm.pl

脚本解析:

utils/run.pl 这个Perl脚本的作用是用多任务执行某个程序,可以独立于Kaldi之外使用。在steps的train_mono.sh、decode.sh等很多脚本都可以通过cmd参数使用它。
utils/run.pl JOB=1:n(数字) /…(路径)/log.JOB.txt echo “This is a job JOB” 该命令同时执行n个echo命令,并发屏幕显示分别写入log.[1-n].txt这n个文本中
Kaldi提供了 run.pl、queue.pl 和 slurm.pl。
数据整理主要有两个目的,一是整理成Kaldi规范的数据文件夹格式;二是划分测试集和训练集。
每个句子被指定了一个唯一ID
wav.scp 记录每个ID的音频文件路径
text 记录每个ID的文本内容 spk2utt 和 utt2spk 记录每个ID的说话人信息
还需要手工准备:
发音词典 lexicon.txt
lexicon_nosil.txt 和 lexicon类似,只是去掉了 phones.txt 音素集,也可硬从lexicon.txt 文件中的所有音素去重得到

task.arpabo 语言模型

prepare_lang.sh 生成语言文件夹 data/lang 存储了待识别的单词集、音素集等信息 local/prepare_lm.sh 把语言模型构建成图

接下来是定义声学特征,它是训练声学模型的基础:执行完生成 feats.scp 记录了声学特征的存储位置
steps/make_mfcc.sh compute_cmvn_stats.sh utils/fix_data_dir.sh

之后是声学模型训练和测试,steps/train_mono.sh

识别也叫解码steps/decode.sh,解码前需要构建状态图utils/mkgraph.sh

解码会使用不同的解码参数生成多个文件,WER有微小的差异,最后找到最好的结果

local 包含用于处理当前示例数据的脚本、识别测试的脚本及除 GMM 训练外其他训练步骤的脚本

steps 和 utils 是两个指向wsj下同名文件夹的链接
steps 是各个训练阶段的子脚本。如:特征提取、单音素的 GMM 训练、三音素的 GMM 训练、神经网络模型训练、解码等
utils 用于协助处理。如:任务管理、文件夹整理、临时文件删除、数据复制和验证等
另外,还有一些特殊的顶层文件或文件夹。如:说话人识别、语种识别、图像识别、神经网络等 P43

数据准备

训练 开发 测试 的用途和分布 不同质量,如清晰程度的数据用于不同阶段和场景 多机训练要确保数据路径在每个节点上都是可直接读写的

排序很重要,按字符串而非数值顺序,例112会排在12前面

p73 例如:创建 utt2spk 时需要注意排序。3.3中讲到 Kaldi 接收多个输入表单,第一个输入表单是按顺序读入的,但对其处理过程中用到的其他表单是按第一个表单元素的索引随机读取的,为了解决管道输入问题,可以约定将所有输入表单按索引裴谞,如3.3的 paste-feats 示例。
有时多个表单单位可能不同,如特征归一化过程中按顺序读取的输入表单是以句子为单位的声学特征,但是随机读取的输入表单是以说话人为单位的归一化系数。在归一化程序处理一个句子的特征数据时,会根据 utt2spk 提供的映射结果,去读取对应说话人的归一化系数。此时,如果 utt2spk 中说话人的排序和 cmvn.scp 中说话人索引的排序方式不同,就会导致可执行程序需要将全部说话人的归一化系数加载到内存中。如果能确保 utt2spk 同时满足句子索引和说话人索引的排序,就可以使用 s 和 cs 声明符提高运算效率。常用方法是将说话人索引作为句子索引的前缀,就可以保证在以句子索引排序 utt2spk 时,说话人索引也是正确的。 有一个特殊情况,说话人长度不确定,可以使用ASCII值比较小的符号,如 “—” 作为说话人索引前缀连接符(LC_ALL=C)。

音频时长表单 get_utt2dur.sh

列表表单用于索引文件,通常以scp做扩展名,可以是物理地址,也可以是管道形式表示的内存地址,指向二进制Kaldi存档文件时还可以增加偏移定位指示从某一字节开始的内容,如aaa.ark:11只从aaa.ark的第11字节开始获取,如果指向的是矩阵文件,还可以进一步扩展,指定所需的行列范围,Kaldi的声学特征是用矩阵表示的,一行对一帧,列对应特征维度,如果需要获取声学特征的前10维,如果aaa.ark保存的是声学特征则可用aaa.ark:11[:,0:9],[:,0:9]表示行列范围,所有行的0~9维,取所有时”:”可以省略简写为[,0:9],行列以“,”分隔
存档表单用于存储数据,可以是文本或二进制,通常以ark做扩展名,但没有严格限制。存档表单没有行的概念,元素之间没有间隔符,对于文本类型的存档表单,必须保证每个元素以换行符结尾。与列表文件区别:p60

rspecifier 读声明符,指定一个输入表单
wspecifier 写声明符,指定一个输出表单

表单属性:

写属性:
表单类型:scp、ark,如果同时输出scp和ark,必须ark在前,逗号分隔:ark,scp:…/aaa.ark,…/aaa.scp
二进制模式标识符b,表示输出表单保存为二进制文件,只对输出存档表单有效,不指定默认就是二进制
文本模式为t,同样只对存档表单有效,因为列表只能是文本
刷新模式,f是表示刷新,nf表示不刷新,用于确定每次写操作之后是否刷新数据流,默认刷新。有利于优化内存使用,通常不用更改
宽容模式p,只对输出列表表单有效。例如,同时输出存档表单和列表表单时,如果表单某个元素对应的存档表单无法获取,那么列表表单中会直接跳过这个元素,不提示错误。
读属性:
输入时,不能用逗号分隔同时输入多个表单文件,可通过多个读声明符实现。
单次访问符号为o,它告知可执行程序,在读入表单中,每个索引只出现一次,不会有多个元素使用同一索引的情况。多次访问为no。
宽容模式,符号p或np,输入的列表表单中某文件不存在或存档表单中元素内容有误,不报错,只打警告。
有序表单,s为有序,ns无序,告知可执行程序,输入表单的元素索引是有序的,按字符串顺序。
有序访问,cs或ncs,告知可执行程序,元素将被顺序访问。
存储格式,新设计不再使用,P66。

由于按字符串顺序,也就是字节的ASCII值排序,所以在Linux中会受到语言环境设置影响。locale命令可查看,其中 LC_COLLATE 决定了排序时的规则。Kaldi的可执行程序是C++写的,LC_COLLATE=C的排序结果与内存表单元素排序结果一致。因此,Kaldi通用脚本中凡涉及调用排序指令时,都会出现 export LC_ALL=C,这个设置只在脚本执行周期内生效。如果需要单独使用可执行程序排序,或在自己编写的脚本中排序,必须添加系统语言设置语句。

utils中有一个data文件夹,其中脚本多是用于处理数据文件夹的。

列表类数据表单:

句子音频表单:wav.scp,可以是切分为每句的或未切分的多句的,如果是未切分的,需要配合切分表(Segments)单使用
声学特征表单:feats.scp,每个元素表示一个句子
谱特征归一化表单:cmvn.scp,通用声学特征处理脚本提取的谱归一化系数文件,可以以句子和说话人为单位
VAD信息表单:vad.scp,表单元素由compute-vad提取,由提取VAD的通用脚本生成,以句子为单位。(语音活动检测(Voice Activity Detection)又称语音端点检测,语音边界检测)

列表类数据表单:

说话人映射表单:utt2spk、spk2utt
标准文本表单:text,每一句的标注,应当是文本归一化后的内容。归一化指,文本中的词都在发音字典和语言模型的词表中,未出现的词都被当做未知词,英文要统一大小写,中文要做好分词。
切分信息表单:segments。Kaldi的数据处理以句子为单位,如果音频没按句子切分,就需要将每一句起止时间记录在 segments 中。文本类型,以句子为索引,索引与音频表单一致,以秒为单位。由句子所属音频文件索引和起止时间组成。p75
VTLN相关系数表单:VTLN Vocal Tract Length Normalisation 声道长度归一化,是一种说话人自适应技术。有三个文本类型的存档文件与此相关:spk2gender说话人性别映射、spk2warp说话人卷曲因子映射和utt2warp句子卷曲因子映射。spk2gender索引是说话人;spk2warp索引说话人,内容是卷曲因子;utt2warp索引是句子,内容是卷曲因子。
句子时长表单:utt2dur。表单可有通用脚本生成,以句子为单位,内容是句子时长,单位秒。

数据文件夹处理脚本

除 wav.scp、text、utt2spk 外都可用通用脚本生成。
spk2utt 在utt2spk脚本基础上生成。feats.sp 有特征提取脚本生成,也可用 copy-feats 转换其他工具提取的声学特征。
如果要使用 VTLN 需要自行准备 spk2gender 文件。utt2warp 和 spk2warp 都可由 gmm-global-est-lvtln-tran 生成。
这些表单都保存在数据文件夹中,数据可以不在同一物理位置。
egs/wsj/utils/data 文件夹钟存放的是用来对数据文件夹进行整体处理的脚本。
通用数据文件夹处理脚本:
|脚本 | 功能| |:–|:–| |combine_data.sh | 将多个数据文件夹合并为一个,并合并对应的表单 | |combine_short_segments.sh | 合并原文件夹中的短句,创建一个新的数据 文件夹 | |copy_data_dir.sh | 复制原文件夹,创建一个新的数据文件夹,可以指定说话人或句子的前缀、后缀,复制一部分数据 | |extract_wav_segments_data_dir.sh | 利用原文件夹中的分段信息,切分音频文件,并保存为一个新的数据文件夹 | |fix_data_dir.sh | 为原文件夹保留一个备份,删除没有同时出现在多个表单中的句子,并修正排序 | |get_frame_shift.sh | 获取数据文件夹的帧移信息,打印到屏幕 | |get_num_frames.sh | 获取数据文件夹的总帧数信息,打印到屏幕 | |get_segments_for_data.sh | 获取音频时长信息,转换为segments文件 | |get_utt2dur.sh | 获取音频时长信息,生成 utt2dur 文件 | |limit_feature_dim.sh | 根据原数据文件夹中的 feats.scp,取其部分维度的声学特征,保存到新创建的数据文件夹中 | |modify_speaker_info.sh | 修改原数据文件夹中的说话人索引,构造“伪说话人”,保存报新创建的数据文件夹中 | |perturb_data_dir_speed.sh | 为原数据文件夹创建一个速度扰动的副本 | |perturb_data_dir_volume sh | 修改数据文件夹中的 wav.scp 文件,添加音量扰动效果 | |remove_dup_utts.sh | 删除原数据文件夹中文本重复超过指定次数的句子,保存到新创建的数据文件夹中 | |resample_data_dir.sh | 修改数据文件夹中的 wav.scp 文件,修改音频采样率 | |shift_feats.sh | 根据原数据文件夹中的 feats.scp 进行特征偏移,保存到新创建的数据文件夹中 | |split_data.sh | 将数据文件夹分成指定数目的多个子集,保存在原数据文件夹中以 split 开头的目录下 | |subsegment_data_dir.sh | 根据一个额外提供的切分信息文件,将原数据文件夹重新切分,创建一个重切分的数据文件夹 | |subset_data_dir.sh | 根据指定的方法,创建一个原数据文件夹的子集,保存为新创建的数据文件夹 | |validate_data_dir.sh | 检查给定数据文件夹的内容,包括排序是否正确、元素索引是否对应等 |

表单索引的一致性

通用脚本中,表单索引分三类:句子、音频、说话人。
音频索引 Recording identifier 对应录音文件。
句子索引 Utterance identifier 处理的基本单元,大部分以句子为索引,最重要的是 text utt2spk feats.scp。在完成声学特征提取后,音频索引就不再使用,整个模型训练过程使用上述三个表单完成,因式它们的索引要保持一致。
说话人索引 Speaker identifier 暴扣 spk2utt 和 cmvn.scp。
说话人信息在自适应声学建模中使用,用来增强识别系统对不同说话人的适应能力,如:倒谱归一化(CMVN)。Kaldi中有两种模式,每句估计一套归一化系数、每个说话人使用一套归一化系数。训练脚本中,cmvn.scp 默认是按照 spk2utt 给出的映射统计每个说话人的归一化系数。

语言模型相关文件

数据文件夹给出了声学模型训练数据的描述,其中文本标注是以词为单位的,在训练之前,需要定义发音词典,音素集和HMM的结构。另外,在进行音素上下文聚类时,还可以通过制定聚类问题的方式融入先验知识。以 Librispeech 为例,这是 stage = 3 阶段进行的。这个阶段执行了3个脚本。第一个用来整理发音词典。第二个根据第一步整理的发音词典生成 data 目录下的语言文件夹 lang。第三个脚本利用语言文件夹生成用于测试的语言模型相关文件。

发音词典与音素集

Librispeech 下载中包含了预先整理好的发音词典、语言模型、语言模型的训练数据,在 data/local/lm 中。在这个文件夹中,有3个关于发音词典的问题件:librispeech-lexicon.txt、librispeech-vocab.txt 和 g2p-model-5。vocab中包含了语言模型训练数据中词频最高的 20W 个词,lexicon中包含了这些词的发音,206508个,比20W多是因为有多音词。
librispeech 的 local/prepare_dict.sh 含5步,0 ~ 2 步用于生成发音词典,3、4步将词典整理为Kaldi的发音词典文件夹。由于发音词典lexicon预先整理好了,所以脚本里 –stage=3。这三步:
 1) 下载 CMU 发音词典,去掉注释文本和多发音标记(在词后加数字区分多音词)。在语音识别中要让多发音的词在文本中保持一致,且Kaldi中使用FST表示词典是支持一个词条存在多个发音路径的,所以直接去掉数字标记即可。
 2) 根据vocab定义的词表统计 CMU 中没有的词条(约2/3),使用 Sequitur 工具生成这些词的发音。这一步要用到 g2p-model-5 文件,这是一个训练好的用于英文发音生成的模型。如果读者有一个发音词典,以及一个词汇表,则可以参考脚本中的命令统计两个文件的交集词汇。
 3) 将生成的发音与 CMU 中已有的发音结合,输出到发音词典中。检查汇总发音词典中词条数目与词汇表数目是否一致。
使用这个发音词典训练出来的模型只能识别20W词,其他词对它来说就是集外词 OOV。 第4步生成4个文件:extra_questions.txt、silence_phones.txt、optional_silence.txt、nonsilence_phones.txt。这些文件定义了音素集,并描述了音素的一些属性。nonsilence_phones包含所有非静音音素,每行一个,重音不同语言处理不同。extra_questions给出了构建音素的声学上下文决策树时会遇到的基本问题,每行对应一个聚类问题。示例中第一个问题对应所有静音音素,最后一个问题对应所有不是静音且不包含重音标记的音素,其余对应使用同一个重音标记的音素。重音对于音素声学表现的影响是独特的,带有不同重音标记的音素不应聚到同类中。在中文中,使用带音调的发音词典,可以将相同音调的音素写成一行,保证在声学聚类中,不同音调的音素不会聚到一起。
optional_silence 定义了填充词间静音的音素。silence_phones定义了所有可以用来表示无效语音(noo-speech)内容的音素。Librispeech 用 SIL 表示静音,用 SPN 表示有声音但无法识别,集外词在Librispeech中就被指定为SPN。
lexicon.txt 包含了前面统计的发音词典的全部内容,和上述无效语音的发音规则,如!SIL用来表示静音的词,发音是静音音素;表示噪音和集外词,发音都是SPN。由此,发音词条扩展为 200 003 条。 nosp 指 no silence probability,未使用silence probability技术。silence probability是Kaldi中一种对词及词间静音的发音概率建模的技术,可以提升语音识别的准确率。总脚本第13步用了这个技术。另外,多发音情况,Kaldi支持定义发音概率。如果不定义,后续脚本会将概率都定为1.0。如果定义,值要在0~1之间,且概率最高的设为 1.0。 带发音概率的词典文件名为 lexiconp.txt。在发音词典文件夹中,lexicon.txt 和 lexiconp.txt 至少存在一个,默认使用 lexiconp.txt。它和前述4个音素属性定义文件,构成Kaldi标准发音词典文件夹。自己搭建训练环境时,需要写脚本创建,validate_dict_dir.pl 用于检查。

语言文件夹

通过Kaldi通用脚本根据发音词典文件夹生成语言文件夹,通常命名为 data/lang。stage 3 完成后会生成3个类似的文件夹:data/lang_nosp、data/lang_nosp_test_tgmed、data/lang_nosp_test_tgsmall,后两个是由第一个复制而来,然后分别添加了各自的 G.fst 文件,这个文件中保存了对应测试集的语言模型。data/lang_nosp在发音词典文件夹内容基础上,进一步整理并扩充了发音词典和音素的属性描述,通过扩充音素集属性的方式 提升 声学模型的性能。
phones.txt 和 words.txt 分别定义了音素索引和词索引。Kaldi中,HMM、发音词典、语言模型都是用 FST 描述的,为了更紧凑的定义FST 并在处理过程中节省文本占用的内容,Kaldi建议用整数索引表示音素和词,而这两个文件的内容就是定义音素与音素索引(如:SIL 1)、词与词索引的映射。
在发音词典文件夹中定义的音素集基础上,这个音素索引文件有3处改动:
 增加了一个空音素;  增加了若干以#开头的音素,这些音素被称为消歧符号,用于区分同音词;  给每个音素增加4个变种,分别表示出现在词头(B)、词中(I)、词尾(E)、单音音素(S)。 前两处改动是为了解决 FST 表示发音词典时的不确定性问题,如:同音词和前缀词等情况。第三处改动扩展音素为位置相关音素,为单词不同位置的音素提供独立的建模能力。L.fst 和 L_disambig.fst 分别对应增加消歧符号前后的发音词典生成的 FST。 words.txt 内容与预先定义的词表想必,增加了4个词条,分别是:句首符号、句尾符号、空符号、消歧符号#0。 句首符号和句尾符号是训练统计语言模型时需要添加的符号,默认,不需要使用者自行处理。空符号和消歧符号是为了FST表示的需要。这些新增的只是占位符,并不实际发音,因此不需要提供对应的发音词典。 oov.txt 和 oov.ini 用于指定集外词(如 )及其索引(如 3)。集外词无法被识别,所以静音词和噪音词也可以用作集外词。 topo文件定义了每个音素的 HMM 拓扑结构,示例中静音和噪音音素用 5 个状态的 HMM 建模,且中间三个状态可以互相跳转,而其他音素使用 3 个状态的左至右 HMM 建模。空音素和消歧符号不实际发音,因此不需要定义 HMM 结构。 语言文件夹中还有一个 phones 文件夹,定义了关于音素的各种属性,例如哪些音素上下文无关,哪些音素在聚类时共享根节点等。发音词典文件夹中的文件内容,除了词典本身外,其他文件用于定义音素属性。而生成的语言文件夹中,基于发音词典文件夹的内容,进一步丰富了音素属性的内容。可以看到某些文件甚至有3种后缀版本,这些文件的内容都是简单的列表,如 silence 文件存储了所有静音音素的列表(如 SIL\n SIL_B...) .txt是音素的文本列表,.int是对应的音素索引,.csl将音素索引合成一行,以冒号分隔。 align_lexicon 文件的内容是发音词典,它将前面的发音词典第一列的词重复了一次,用来处理词网络文件和一些识别结果文件(如 !SIL !SIL SIL_S)。 context_indep 文件的内容是所有上下文无关音素的列表。列表中的音素聚类时不考虑上下文。在 Librispeech 中,所有静音音素都被定义为上下文无关音素。 disambig 文件内容是所有消歧符号列表,也就是音素列表中以#开头的部分音素。 extra_questions 用于音素上下文聚类,在发音词典文件夹同名文件基础上增加了音素位置标记,并对静音音素的聚类方法做了修改。 nonsilence 所有非静音符号、非消歧符号的音素列表。 optional_silence 词间选择性填充的静音音素列表,与发音词典文件夹中同名文件一样。 sets 定义了音素组;roots 定义了哪些音素共享上下文决策树的同一根节点。这两个文件用于上下文聚类。 silence 所有静音音素的列表。 wdisambig_phones、wdisambig_words、wdisambig 分别是消歧符号音素的索引、消歧符号词的索引、消歧符号文本。 word_boundary 每个音素的词位置,使用场合与 align_lexicon 类似。

生成与使用语言文件夹

utils/prepare_lang.sh 生成语言文件夹。第一个参数时发音词典文件夹;第二个参数是集外词符号,理论上可以选择发音词典中任意词做集外词,通常单独定义一个或用静音;第三个参数时临时文件夹,用于保存生成过程中比较重要的中间文件;最后一个参数时输出的语言文件夹目录。可选参数可用来指定语言文件夹中文件的特性,如静音音素的状态数、非静音音素的状态数、是否使用音素位置标志等。
中间文件:
 align_lexicon.txt 扩展音素位置信息的发音词典
 lex_ndisambig 用作消歧符号的音素的个数
 lexiconp.txt 扩展音素位置信息的带发音概率的发音词典,
 lexiconp_disambig.txt 带音素位置信息、发音概率、消歧符号的发音词典,用于生成 L_disambig.fst
 phone_map.txt 同一个音素不同版本(如音素位置)的对应关系
 Kaldi个阶段通用声学模型训练脚本都会用到语言文件夹中的文件,除 HMM 拓扑结构文件外,都不建议手动修改。HMM 文件只要涵盖了所有非消歧音素,是可以手动修改的。

经典声学建模技术

经典语音识别基于 GMM-HMM;端到端距离产业落地还有一定距离;基于深度神经网络的主流是混合(Hybird)系统,它也是基于 HMM 的,只是使用神经网络代替 GMM 来建模 HMM 状态的观察概率,而建模、解码等识别流程的各个模块仍然沿用了经典语音识别技术的方法。
Librispeech 中经典语音识别技术的模型训练是从第8阶段开始的10个阶段,每个阶段使用了不同的建模方法和不同的数据训练不同的模型,并解码测试:
 stage 8:单音子模型
 9:三音子模型
 10:带 LDA + MLLT 特征变换的模型
 11:使用1W句作为训练集的子集,训练带说话人自适应的模型
 12:使用 clean_100 子集训练带说话人自适应的模型
 13:使用静音概率修改发音词典
 14:基于 nnet2 的神经网络示例,以弃用
 15、16:将 clean_100 和 clean_360 子集合并训练带说话人自适应的模型
 17、18:将上述数据与 other_500 子集合并训练带说话人自适应的模型
每个阶段都是在上一阶段基础上,获取更精细的声学状态对齐,训练更优模型。后面几个步骤是可选的,一般到第10阶段就可以得到可用的模型了。

特征提取

用Kaldi提取声学特征

时域信号很难找规律,人类的听觉器官是通过频域而不是波形来辨认声音的。把声音进行短时傅里叶变换(Short-time Fourier Transform,STFT)就得到了声音的频谱。因此,以帧为单位,依据听觉感知机理,按需调整声音片段频谱中各个成分的幅值,并将其参数化,得到适合表示语音信号特征的向量就是声学特征(Acoustic Feature)。近来端到端中有研究波形直接做输入的,但是目前还是传统的提取技术为主流。
声学特征把波形分成若干离散的帧,整个波形可以看做一个矩阵。p96 波形被分为很多帧,每帧都用一个12维的向量表示,色块的颜色深浅表示向量值的大小。其中,梅尔频率倒谱系数(Mel-Frequency Cepstral Coefficients,MFCCs)是最常见的声学特征,提取流程:
1) 对语音滑动加窗来实现分帧。通常帧长25毫秒,帧移10毫秒,这样相隔两帧会重叠15毫秒,可以保证帧内信号的平稳性,并使帧之间有交叠。
2) 对每一帧做快速傅里叶变换(Fast Fourier Transform,FFT),并计算功率谱。
3) 对功率谱应用梅尔滤波器组,获取每个滤波器内的对数能量作为系数。
4)对得到的对数能量向量做离散余弦变换(Discrete Cosine Transform,DCT)。

通过设定 DCT 的输出个数,可以得到不同维度的 MFCCs 特征。p96的4-2使用了12维输出。除滤波器个数外,Kaldi 的 MFCCs 提取工具 compute-mfcc-feats 还有很多其他参数可调。mfcc.conf 和 mfcc_hires.conf 里有比较常用的参数示例,分别提取 13 维 MFCCs 特征和 40 维高分辨率 MFCCs 特征。这个工具根据音频表单输入的WAV文件列表提取 MFCCs 特征,可以独立于Kaldi使用。
除 MFCCs 外,FilterBank、PLP 也是常用的特征。FilterBank 有时也写作 FBank,是不做DCT 的 MFCCs,保留了特征维间的相关性,在用卷积神经网络作为声学模型时,通常选用 FBank 作为特征。工具:compute-fbank-feats。
PLP 特征提取自线性预测系数(Linear Prediction Coefficients,LPC),几种特征的关系 p99 4-3。工具:compute-plp-feats。

特征在Kaldi中的存储

如果用 Kaldi 工具如 compute-mfcc-feats 提取特征,会自动输出为表单形式,可以用 ark、ark,t、scp 等浅醉控制表单格式。也可以用其他工具提供的声学特征进行训练,只要保存成 Kaldi 的表单格式即可。一种最简单示例 p100 句子ID [ 声学特征 每行一帧 ],转换为二进制形式即可:copy-matrix ark,t:feat_in_text.ark ark:feat_in_binary.ark
提取保存原则:
 输出二进制存档表单,而不是输入单个特征文件;  使用管道构建特征处理流程;  分离数据文件夹与特征存档文件。

Librispeech 示例中 run.sh 第 5 阶段用于创建一个分散的存储环境。对于比较大的语料,都放在提交任务的机器上会影响读写速度,Kaldi提供了脚本可以在多个指定地址创建存储文件,并使用连接文件的形式方便访问。Librispeech 中将声学特征保存在 mfcc 文件夹中:mfccdir=mfcc。这是一个相对路径,如果不分散存储,所有特征文件都保存在这,创建分散存储脚本:utils/create_split_dir.pl 会在 mfcc 中建立链接文件,指向其他存储地址。提取特征时,会自动检测到这些分散存储的链接文件,并将特征文件分散在这些目录中。而数据文件夹中,保存了一份所有特征表单的汇总列表,即声学特征表单。
第 6 阶段执行提取特征,生成 feats.scp 和 cmvn.scp 表单。分两步:第一步从音频文件中提取基础声学特征,要求音频采样大小为 16 比特,输出是声学特征表单和用于保存特征的二进制文档,3个参数:数据文件夹、运行目录主要是保存日志文件、特征文件保存目录可以是分散存储;第二步是倒谱均值方差归一化(Cepstral Mean and Variance Normallization,CMVN)系数的计算,参数与特征提取类似。脚本读取声学特征表单和说话人映射表单,计算每个说话人的倒谱均值方差归一化系数。可以用 copy-matrix 指令观察其输出的内容:copy-matrix ark:xxx/cmvn_xxxxx.ark ark,t:- 。该表单元素以说话人为索引,每个方括号内是其对应的倒谱均值方差归一化系数,两段分别对应均值归一化系数和方差归一化系数。Kaldi训练脚本中,大都默认用谱归一化,以使得模型的输入特征趋近正态分布,这一点对于说话人无关的声学建模非常重要。但在线解码时,CMVN 的计算可能和离线训练的 CMVN 不一致,这是实际工程中常见的问题,细节见后文。
第 7 阶段使用提取子集的脚本,以 train_clean_100 为蓝本,创建3个不同子集用于声学模型训练的不同阶段,使得Kaldi的训练环境便于灵活处理和使用数据p102?。

特征的使用

特征提取完成之后,可以通过数据文件夹中的声学特征表单 feats.scp 和倒谱均值方差归一化系数表单 cmvn.scp 获取归一化的特征。
在训练声学模型时,通常还要对特征做更多的扩展。例如 Kaldi 的单因子模型训练,在谱归一化 (CMVN) 的基础上做了差分系数 (Delta) 的扩展。在训练脚本 steps/train_mono.sh 中可以看到构建这个管道的方法 :

feats=" ark,s,cs:apply-cmvn $cmvn opts --utt2spk=ark:$sdata/JOB/utt2spk scp:$sdata/JOB/cmvn.scp scp:$sdata/JOB/feats.scp ark:- | add-deltas $delta-opts ark:- ark:- |"

而在说话人自适应训练中,在 CMVN 的基础上做了前后若干帧的拼接,然后使用 LDA 的矩阵降维。可以在脚本 steps/train_sat.sh 中找到其管道的构建方法:

sifeats="ark,s,cs:apply-cmvn $cmvn_opts --utt2spk=ark:$sdata/JOB/utt2spk scp:$sdata/JOB/cmvn.scp scp:$sdata/JOB/feats.scp ark:- | splice-feats $splice_opts ark:- ark:- | transform-feats $alidir/final.mat ark:- ark:- |"

这种通过基础特征配合碎片化工具和管道的方法,使得训练过程中的特征选择更灵活。 例如想使用特征中的某些维度进行训练,就可以使用 utils/limit_feature_dim.sh 直接创建一个新的数据文件夹,而无需重新提取特征。在中文示例中,就出现了在某些阶段使用谱特征加基频的训练方法,而在某些阶段只用谱特征的训练方法。另外,为了加速训练,在可以并行的训练阶段,大部分脚本会根据指定的并行任务数将数据文件夹拆分为若干份。在数据文件夹中,可以看到以 split 开头的文件夹,里面的每个文件夹都包含一部分数据,结构与完整数据文件夹相同, 是数据文件夹子集。

常用特征类型

如前文所说常用的声学特征包括 MFCC、Fbank、PLP 等。另外,在中文语音识别里还常用基频。Kaldi 对这几种常用的声学特征都支持,在 step 中可以看到这些脚本:
| 脚本 | 作用 | 配置文件(conf 文件夹下) | |:–|:–|:–| | make_mfcc.sh | 提取 MFCC 特征 | mfcc.conf | | make_mfcc_pitch.sh | 提取 MFCC 加基频特征 | mfcc.conf, pitch.conf | | make_mfcc_pitch_online.sh | 提取 MFCC 加在线基频特征 | mfcc.conf, pitch_online.conf | | make_fbank.sh | 提取 Fbank 特征 | fbank.conf | | make_fbank_pitch.sh | 提取 Fbank 加基频特征 | fbank.conf, pitch.conf | | make_plp.sh | 提取 PLP 特征 | plp.conf | | make_plp_pitch.sh | 提取 PLP 加基频特征 | plp.conf, pitch.conf |

在训练 GMM 声学模型时,由于计算量的限制,通常使用 对角协方差矩阵,因此 GMM 概率密度函数的各维度之间是条件独立的,所以通常使用 MFCC(由于做了DCT?) 特征,并通过 LDA 等方法进一步解耦。而在训练神经网络,尤其是卷积神经网络的声学模型时,通常使用 FBank 特征。需要注意的是,在提取上述三种谱特征时,有一个参数 dither ,默认值为 1,作用是在计算滤波器系数能量时加入随机扰动,防止能量为零的情况出现。但是这样会造成同一条音频的输出特征前后不一致,如果需要保持一致,则要在配置文件中设置 “–dither=0”。
Kaldi的基频提取分为两步:第一步是输出二维基频特征。分别是以 Hz 为单位的基频和表示其置信度的归一化相关系数 (NCCF); 第二步是在此基础上将其处理成适合语音识别系统的特征。可选特征包括:
  1.规整的NCCF,用于表示一帧语音是浊音的概率(POV),即其基频的置信度;
  2.POV加权并减去 1.5 秒(左右各 750 毫秒)窗内均值的对数基频;
  3.对数基频的一阶差分;
  4.原始对数基频,这个值默认不输出。
需要注意的是,按照默认配置的基频特征处理流程,减均值这一步需要较长的窗,会影响实时语音识别的响应速度,可以调节计算均值的窗长,具体方法可参考 Librispeech 中的在线基频配置文件 conf/online_pitch.conf。
除使用 Kaldi 自带的特征提取功能外,还可以使用 HTK 和 Sphinx 格式的声学特征。例如已有 HTK 格式的特征列表:data/feats_htk.scp,可以使用命令 copy-feats --hkt-in scp:data/feats_htk.scp ark,scp:mfcc/mfcc_htk.ark,mfcc/mfcc_htk.scp 转化为 Kaldi 声学特征表单。

单因子模型的训练

本章介绍的声学模型特指经典的基于隐马尔科夫模型(Hidden Markov Model,HMM) 语音识别框架中的声学模型。本节将关注一种基本的模型结构:使用高斯混合模型(Gaussian Mixture Model,GMM) 描述单音子(Monophone) 发音状态的概率分布函数(Probability Distribution Function,PDF) 的 HMM 模型。这种结构的声学模型在原理和训练流程上,是训练其他声学模型的基础。

声学模型的基本概念

在经典的语音识别框架中,一个声学模型就是一组 HMM。一个 HMM 的参数由初始概率、转移概率、观察概率三部构成。对于语音识别框架中的声学模型里的每个 HMM。应当定义该 HMM 中有多少个状态,以及各个状态起始的马尔科夫的初始概率,各状态间的转移概率及每个状态的概率分布函数。

实践中,一般设初始概率恒为1,因此不必在模型中记录。转移概率对识别结果的影响很小,甚至有时可以忽略。所以实践中经常把状态的转移概率预设为固定值,不在训练中更新转移概率。声学模型包含的信息,主要是状态定义和各状态的观察概率分布。如果用高斯混合模型对观察概率分布建模,那么就是 GMM-HMM 模型;如果用神经网络模型对观察概率分布建模,那么就是 NN-HMM 模型。

HMM 状态的物理意义在语音识别中可以认为是音素的发音状态。习惯上把一个音素的发音状态分为三个部分,分别是:”初始态“、”稳定态“、”结束态“。对应的,一个音素的发音用三个 HMM 状态建模。

需要说明的是,音素建模的状态不一定是三个,只是在传统语音识别方法中用三个状态对音素建模更加常见。目前 Kaldi 主推的 chain model 使用两个状态的建模方法来建模音素的起始帧和其他帧。而在传统的基于 HMM 的语音合成(TTS) 中更常用5个状态对音素建模。

根据声学模型,可以计算某一帧声学特征在某一状态上的声学分(AM score)。这里所说的声学分,指的是该帧声学特征对于该状态的对数观察概率,或者称为对数似然值(Log-likelihood):
            AMScore(t,i) = logP(ot|si)
~t t~
在上式中,AMScore(t,i) 是第 t 帧语音特征 ot 在第 i 个状态 si 上的声学分。
观察概率的经典建模方法是高斯混合模型(Gaussian Mixture Model,GMM)。GMM 的思路是使用多个高斯分量加权叠加,拟合出任意分布的概率密度函数。p109图
GMM 建模观察概率可以用公式表示:

https://saaavsaaa.github.io/jax/escape.html
\(logP(o_t|s_i) = log\sum_{m=1}^{M}\frac{c_{i,m}exp(-\frac{1}{2}(o_t-μ_{i,m})^T(∑_{i,m}^{-1})(o_t-μ_{i,m}))}{(2π)^{\frac{D}{2}}|∑_{i,m}|^{\frac{1}{2}}}\)

\[logP(第t帧语音特征|第i个状态) = log\sum_{m=1}^{M}\frac{c_{i,m}exp(-\frac{1}{2}(第t帧语音特征-状态s_i的第m个高斯分量的D维均值向量)^T(状态s_i的第m个高斯分量的协方差矩阵^{-1})(第t帧语音特征-状态s_i的第m个高斯分量的D维均值向量)}{(2π)^{\frac{D}{2}}|状态s_i的第m个高斯分量的协方差矩阵|^{\frac{1}{2}}}\]


在上式中,μi,m 为状态 si 的第 m 个高斯分量的 D 维均值向量,∑i,m 为状态 si 的第 m 个高斯分量的协方差矩阵。在声学模型训练中,为了降低模型参数量,通常令协方差矩阵为对角阵:∑i,m = diag(δi,m) ,其中δi,m为D维向量。

一个 GMM-HMM 声学模型存储的主要参数为各状态和高斯分量的 μi,m、δi,m、ci,m。使用 Kaldi 提供的模型复制工具可以方便地查看声学模型的内容。2.4小节提到过的一个 GMM-HMM 声学模型,存储为 final.mdl。 使用命令可以把这个模型转换成文本格式查看:gmm-copy – binary=false final.mdl final.mdl.txt。内容提供在p110


edit