接上回,mfcc特征咱们暂时看完了,总结一下,此处引用别人的分析:
compute-mfcc-feats.cc
Create MFCC feature files.
Usage: compute-mfcc-feats [options...]
其中参数rspecifier用于读取.wav文件,wspecifier用于写入得到的MFCC特征。典型应用中,特征将被写入到一个大的”archive”文件,同时会写入一个”scp”文件用于随机存取。该程序并未提取delta特征(add-delats.cc).
其–channel参数用于选择立体声情况(–channel=0, –channel=1).
compute-mfcc-feats --config=conf/mfcc.conf \
scp:exp/make_mfcc/train/wav1.scp \
ark:/data/mfcc/raw_mfcc_train.1.ark;
第一个参数”scp:…”用于读取exp/make_mfcc/train/wav1.scp指定的文件。第二个参数”ark:…”指示计算得到的特征写入归档文件/data/mfcc/raw_mfcc_train.1.ark。归档文件里的每一句是N(frames)× N(mfcc)的特征矩阵。
MFCC特征的计算是在对象MFCC中的compute方法完成的,计算过程如下:
1.遍历每一帧(通常25ms一帧,10ms滑动)
2.对每一帧
a.提取数据,添加可选扰动,预加重和去直流,加窗
b.计算该点的能量(使用对数能量,而非C0)
c.做FFT并计算功率谱
d.计算每个梅尔频点的能量,共计23个重叠的三角频点,中心频率根据梅尔频域均匀分布。
e.计算对数能量,做离散余弦变换,保留指定的系数个数
f.倒谱系数加权,确保系数处于合理的范围。
三角梅尔频点的上下限由–low-freq和–high-freq决定,通常被分别设置成接近0和奈奎斯特频率。如对于16KHz语音–low-freq=20, –high-freq=7800。
可以使用copy-feats.cc将特征转换成其它格式。
关于梅尔倒谱系数(MFCC)我们之前讲过,在Kaldi里它本身设置了合理的默认值,同时保留了一部分用户最有可能想调整的选项,如梅尔滤波器的个数,最大和最小截止频率等等.它通常需要读取wav文件或.pcm文件,假如数据源不是wav文件,我们就得使用工具来转化,Kaldi中有的sph2pipe工具能满足一般的情况.
命令工具 compute-mfcc-feats用来计算MFCC特征,若直接运行不带参数的话就会给出一个参数列表.改程序需要两个参数,rspecifier是用来读.wav数据, wspecifier是用来写特征的(就是r 和w啦).典型的用法是,将数据写入一个大的”archive”文件,也写到一个”scp”文件以便随机读取.
MFCC的计算由Mfcc类型的对象完成,它有Compute()函数可以根据波形计算特征.一个完整的MFCC计算如下:
1) 计算出一个文件中帧的数目(通常帧长25ms,帧移10ms)
2) 对每一帧提取数据,可选做Dithering,预加重和去除直流偏移,还可以和加窗函数想成(此处支持多种选项,如Hanmming 窗).
3)计算该点能量(假如用对数能量则没有C0C0).
4) 做快速傅里叶变换(FFT)并计算功率谱.
5)计算每个梅尔滤波器的能量,如23个部分重叠的三角滤波器,其中心在梅尔频域等间距.
6) 计算对数能量病作宇轩变换,根据要求保留系数(如13个).
7) 选做倒谱变;它仅仅是比例变换,确保系数在合理范围内.
上下截止频率根据三角滤波器界定,由选项–low-freq和–high-freq控制,通常分别设置为0Hz和奈奎斯特频率附近,如对16kHz采样的语音设置为–low-freq=20 和 –high-freq=7800。
Kaldi的特征和HTK的特征在很多方面不同,但是几乎所有这些不同归结于有不同的默认值。用选项–htk-compat=true并正确设置参数,能得到同HTK非常接近的特征。一个可能重要的选项是我们不支持能量最大归一化。这是因为Kaldi希望能把无状态方式应用到归一化方法,且希望从原理上计算一帧帧特征仍能给出相同结果。但是程序compute-mfcc-feats里有–subtract-mean选项来提取特征的均值。对每个语音做此操作;每个说话人可以有不同的方式来提取特征均值。(如compute_cmvn_stats.sh,表示倒谱均值和方差归一化)。
再回到run.sh中
我们看到
for x in train dev test; do
#make mfcc 生成mfcc
steps/make_mfcc.sh --nj $n --cmd "$train_cmd" data/mfcc/$x exp/make_mfcc/$x mfcc/$x || exit 1; #调用steps/make_mfcc.sh ,$n是cpu的并发数,--cmd "$train_cmd"是 训练的cmd ,它调用的是 cmd.sh中设置的train_cmd ,data/mfcc/$x 是每个数据的目录,exp/make_mfcc/$x, mfcc/$x 这些都是目录的参数
#compute cmvn 计算cmvn
steps/compute_cmvn_stats.sh data/mfcc/$x exp/mfcc_cmvn/$x mfcc/$x || exit 1;
done
接下来是CMVN(Cepstral mean and variance normalization)倒谱均值和方差归一化
倒谱均值和方差归一化包括归一化原始倒谱的均值和方差,通常给出零均值,单位方差倒谱,基于每个话语或每个说话者。 我们提供代码来支持这个,以及一些示例脚本,但我们并不特别推荐使用它。 一般而言,我们更喜欢基于模型的方法来进行均值和方差归一化; 例如,我们的线性VTLN(LVTLN)代码也学习平均偏移,指数变换(ET)的代码执行对角CMLLR变换,其具有与倒谱平均值和方差归一化相同的功效(通常应用于完全扩展的特征除外)。对于非常快速的操作,可以使用具有基于电话的语言模型的非常小的模型来应用这些方法,并且我们的一些示例脚本证明了这一点。 在特征提取代码中还有能力在每个语句的基础上减去平均值(compute-mfcc-feats 和 compute-plp-feats 减去平均值)。
为了支持每个话语和每个说话者的均值和方差归一化,我们提供了程序compute-cmvn-stats和apply-cmvn。默认情况下,程序compute-cmvn-stats将计算均值和方差归一化的足够统计量,作为矩阵(格式不是很重要;请参阅代码以获取详细信息),并将写出这些统计索引的表 通过话语id。如果给出-spk2utt选项,它将在每个说话者的基础上写出统计信息(警告:在使用此选项之前,读取在随机访问模式下读取存档时避免内存膨胀,因为此选项会导致输入功能 以随机访问模式读取)。程序“apply-cmvn”读入特征和倒谱均值和方差统计; 如果应用了-utt2spk选项,则默认情况下,每个语句都会对统计信息进行索引,或者每个说话者都会对其进行索引。 它在均值和方差归一化后写出了特征。 这些程序尽管有名字,但并不关心这些特征是否由倒谱或其他任何东西组成; 它只是将它们视为矩阵。 当然,提供给compute-cmvn-stats和apply-cmvn的功能必须具有相同的维度。
我们注意到它可能与特征转换代码的整体设计更加一致,提供一个版本的compute-cmvn-stats,可以写出平均值和方差,将变换归一化为泛型仿射变换(格式与 CMLLR转换),以便它们可以由程序transform-feats应用,并根据需要使用compose-transforms与其他转换组合。 如果需要,我们可以提供这样的程序,但由于我们不将均值和方差归一化视为任何配方的重要部分,我们还没有这样做。
该归一化通常是为了获得基于说话人或者基于说话语句的零均值,单位方差归一化特征倒谱。但是并不推荐使用这个方法,而是使用基于模型的均值和方差归一化,如Linear VTLN(LVTLN)。可以使用基于音素的小语言模型进行快速归一化。特征提取代码compute-mfcc-feats.cc/compute-plp-feats.cc同样提供了–substract-mean选项获得零均值特征。如果要获得基于说话人或者基于句子的均值和方差归一化特征,可以使用
compute-cmvn-states.cc或者apply-cmvn.cc程序。
compute-cmvn-stats.cc将会计算均值和方差需要的所有统计量,并将这些统计信息以矩阵的形式写入到table中。
compute-cmvn-stats
--spk2utt=ark:data/train/train.1k/spk2utt \
scp:data/train.1k/feats.scp \
ark:exp/mono/cmvn.ark;
在实际情况下,受不同麦克风及音频通道的影响,会导致相同音素的特征差别比较大,通过CMVN可以得到均值为0,方差为1的标准特征。均值方差可以以一段语音为单位计算,但更好的是在一个较大的数据及上进行计算,这样识别效果会更加robustness。Kaldi中计算均值和方差的代码在compute-cmvn-stats.cc, 归一化在apply-cmvn.cc。
我们下面来看看compute-cmvn-stats.sh中的内容
# Compute cepstral mean and variance statistics per speaker.
# We do this in just one job; it's fast.
# This script takes no options.
计算每个发言者的倒频谱均值和方差统计数据。我们只用一个工作来做这个; 它很快。这个脚本没有选项。
# Note: there is no option to do CMVN per utterance. The idea is
# that if you did it per utterance it would not make sense to do
# per-speaker fMLLR on top of that (since you'd be doing fMLLR on
# top of different offsets). Therefore what would be the use
# of the speaker information? In this case you should probably
# make the speaker-ids identical to the utterance-ids. The
# speaker information does not have to correspond to actual
# speakers, it's just the level you want to adapt at.
注意:没有选项可以根据每段话语进行CMVN。 也就是,如果你按照每段话语进行CMVN,那么在每个说话者上进行fMLLR是没有意义的(因为你将在不同的偏移量之上进行fMLLR)。 那么说话者信息的用途是什么? 在这种情况下,您应该使说话者ID与话语-id相同。 说话者信息不必与实际说话者相对应,它只是您想要适应的级别。
echo "$0 $@" # Print the command line for logging #打印命令行日志
将后面的注释掉,我们运行./run.sh,看一下
图中的打印就是echo打印出来的,继续
fake=false # If specified, can generate fake/dummy CMVN stats (that won't normalize) #如果指定,可以生成假的/虚拟的CMVN统计(不会规范化)
fake_dims= # as the "fake" option, but you can generate "fake" stats only for certain
# dimensions. #作为“假”选项,但您只能为某些维度生成“假”统计数据
two_channel=false #应该是双通道的意思
这块应该是传送一些参数,但是这些参数在实际运行run.sh中并没有传递,我们加入一些打印验证一下
if [ "$1" == "--fake" ]; then
echo "--fake"
fake=true
shift
fi
if [ "$1" == "--fake-dims" ]; then
echo "--fake-dims"
fake_dims=$2
shift
shift
fi
if [ "$1" == "--two-channel" ]; then
echo "--two-channel"
two_channel=true
shift
fi
我们运行之后发现没有打印,说明并没有调用这里,我们继续
-lt 表示小于 $# 表示这个程序的参数个数 -gt 表示大于
if [ $# -lt 1 ] || [ $# -gt 3 ]; then
echo "Usage: $0 [options]
echo "e.g.: $0 data/train exp/make_mfcc/train mfcc"
echo "Note:
echo "Options:"
echo " --fake gives you fake cmvn stats that do no normalization."
echo " --two-channel is for two-channel telephone data, there must be no segments "
echo " file and reco2file_and_channel must be present. It will take"
echo " only frames that are louder than the other channel."
echo " --fake-dims
echo " dimensions (e.g. 13:14:15)"
exit 1;
fi
我们运行之后发现没有打印,说明并没有调用这里,我们继续
if [ -f path.sh ]; then . ./path.sh; fi #如果存在path.sh 则调用./path.sh
我们在path.sh中加入一些打印看看是否是调用了
我们发现确实是调用了,我们看到是在运行run.sh是要用到的环境变量,在这里重新在设置一下.
data=$1 #将第一个参数赋值到data
if [ $# -ge 2 ]; then #-ge表示大于等于 $# 表示这个程序的参数个数
echo "----$2"
logdir=$2 #将第二个参数赋值到logdir
else
logdir=$data/log
fi
if [ $# -ge 3 ]; then
echo "----$3"
cmvndir=$3 #将第三个参数赋值到cmvndir
else
cmvndir=$data/data
fi
为了判断调用流程,我们加入一些打印信息,运行./run.sh,显示
说明这两处都调用了,也就说明logdir和cmvndir都被赋了相应的值。我们接着继续
# make $cmvndir an absolute pathname. #这块是调用perl脚本来创建目录,给cmvndir一个绝对路径名称
cmvndir=`perl -e '($dir,$pwd)= @ARGV; if($dir!~m:^/:) { $dir = "$pwd/$dir"; } print $dir; ' $cmvndir ${PWD}`
# use "name" as part of name of the archive. #使用“名称”作为文件名称的一部分
name=`basename $data`
创建两个目录
mkdir -p $cmvndir || exit 1;
mkdir -p $logdir || exit 1;
我们接着继续
required="$data/feats.scp $data/spk2utt" #将两个文件赋值给required
for f in $required; do #循环遍历两个文件 检测是否存在这两个文件
if [ ! -f $f ]; then
echo "make_cmvn.sh: no such file $f"
exit 1;
fi
done
我们继续,以下是先后判断$fake、$two_channel、"$fake_dims"是否为空;三个条件,三个条件都不满足,运行下面的else
if $fake; then
dim=`feat-to-dim scp:$data/feats.scp -`
! cat $data/spk2utt | awk -v dim=$dim '{print $1, "["; for (n=0; n < dim; n++) { printf("0 "); } print "1";
for (n=0; n < dim; n++) { printf("1 "); } print "0 ]";}' | \
copy-matrix ark:- ark,scp:$cmvndir/cmvn_$name.ark,$cmvndir/cmvn_$name.scp && \
echo "Error creating fake CMVN stats. See $logdir/cmvn_$name.log." && exit 1;
elif $two_channel; then
! compute-cmvn-stats-two-channel $data/reco2file_and_channel scp:$data/feats.scp \
ark,scp:$cmvndir/cmvn_$name.ark,$cmvndir/cmvn_$name.scp \
2> $logdir/cmvn_$name.log && echo "Error computing CMVN stats (using two-channel method). See $logdir/cmvn_$name.log." && exit 1;
elif [ ! -z "$fake_dims" ]; then
! compute-cmvn-stats --spk2utt=ark:$data/spk2utt scp:$data/feats.scp ark:- | \
modify-cmvn-stats "$fake_dims" ark:- ark,scp:$cmvndir/cmvn_$name.ark,$cmvndir/cmvn_$name.scp && \
echo "Error computing (partially fake) CMVN stats. See $logdir/cmvn_$name.log" && exit 1;
else
echo "----test" #我们加入一些打印判断流程
! compute-cmvn-stats --spk2utt=ark:$data/spk2utt scp:$data/feats.scp ark,scp:$cmvndir/cmvn_$name.ark,$cmvndir/cmvn_$name.scp \
2> $logdir/cmvn_$name.log && echo "Error computing CMVN stats. See $logdir/cmvn_$name.log" && exit 1;
fi
在实际输出中已经输出了"----test",所以证明进入了这里
这里的意思是如果compute-cmvn-stats这个命令执行成功并且没有标准错误,就不会在exp/mfcc_cmvn/cmn_train.log中记录错误日志,如果执行不成功,则在这个文件中输出信息。我们看一下exp/mfcc_cmvn/cmn_train.log
其中记录了这个命令执行的具体参数信息,同时在/opt/kaldi/egs/thchs30/s5/mfcc/train下还会生成两个文件
ark为二进制文件,scp为记录ark位置,其实与mfcc生成文件的方法是一样的。我们继续
接下来是拷贝cmvn_train.scp文件到
cp $cmvndir/cmvn_$name.scp $data/cmvn.scp || exit 1;
wc命令是统计命令,如文件的字符数等,wc -l是统计行数
-ne 表示 不等于
nc=`cat $data/cmvn.scp | wc -l` #统计$data/cmvn.scp文件行数
nu=`cat $data/spk2utt | wc -l` #统计$data/spk2utt文件行数
if [ $nc -ne $nu ]; then #实际上就是如果$data/cmvn.scp文件行数 不等于 $data/spk2utt文件行数
echo "$0: warning: it seems not all of the speakers got cmvn stats ($nc != $nu);" #打印它看起来不是所有的说话者都得到了cmvn处理
[ $nc -eq 0 ] && exit 1; #如果$data/cmvn.scp文件行数-eq等于0就退出
fi
echo "Succeeded creating CMVN stats for $name" #打印成功创建CMVN统计
未完待续。。。。。。