kaldi中文语音识别_基于thchs30(7)

接上回,mfcc特征咱们暂时看完了,总结一下,此处引用别人的分析:

特征提取

MFCC

compute-mfcc-feats.cc

Create MFCC feature files.
Usage:  compute-mfcc-feats [options...] <wav-rspecifier> <feats-wspecifier>

其中参数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] <data-dir> [<log-dir> [<cmvn-dir>] ]";
   echo "e.g.: $0 data/train exp/make_mfcc/train mfcc"
   echo "Note: <log-dir> defaults to <data-dir>/log, and <cmvn-dir> defaults to <data-dir>/data"
   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 <n1:n2>  Generate stats that won't cause normalization for these"
   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统计

未完待续。。。。。。

猜你喜欢

转载自blog.csdn.net/dqxiaoxiao/article/details/81867306