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

接上回,完成数据准备的工作后,shell显示

说明已经创建这些文件完毕。 我们看到生成的spk2utt的文件格式是这样的:

我们看到它是先是一个说话者id号 然后是这个说话者id下的所有名称,

而word.txt和text是一样的。

既然我们已经数据准备了,所以以后就不用再进行数据准备了,所以在下次运行run.sh时,  先注释掉相关数据准备的shell脚本。
#data preparation
#generate text, wav.scp, utt2pk, spk2utt

#local/thchs-30_data_prep.sh $H $thchs/data_thchs30 || exit 1;

我们接下来继续看run.sh    
#produce MFCC features 生成 mfcc特征

rm -rf data/mfcc && mkdir -p data/mfcc &&  cp -R data/{train,dev,test,test_phone} data/mfcc || exit 1;
#先删除data下的mfcc目录, 并且创建mfcc目录,拷贝这些数据到mfcc目录
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

咱们先来看看steps/make_mfcc.sh是怎么样提取mfcc特征的:

#!/bin/bash

# Copyright 2012-2016  Johns Hopkins University (Author: Daniel Povey)
# Apache 2.0
# To be run from .. (one directory up from here)
# see ../run.sh for example

# Begin configuration section.
nj=4 #默认nj为4
cmd=run.pl #调用run.pl
mfcc_config=conf/mfcc.conf  #mfcc配置文件为conf/mfcc.conf  此时说明一下  我们看到 配置文件中只有如下两行:
--use-energy=false   # only non-default option.
#--sample-frequency=8000    说明没有选定使用能量,也就是不使用能量
compress=true   #这里应该是启用了压缩
write_utt2num_frames=false  # if true writes utt2num_frames  这里表示如果选择true 则就写utt2num_frames
# End configuration section.

echo "$0 $@"  # Print the command line for logging   这里是打印命令行到日志,$0为执行的命令 $@表示所有参数脚本的内容

if [ -f path.sh ]; then . ./path.sh; fi    #这句的意思是如果存在path.sh那么就执行它,为了测试它调用了,我们一会在path.sh中添加一些打印,来查看。

我们先看到这,将后面的先注释掉。
我们还需要在path中添加一些打印来方便查看,我们在path.sh中的开始部分加入
echo  "come here,for test"  ,如果打印多次,说明run.sh中的for循环通过make_mfcc.sh调用了path.sh多次。

我们调试一下,将run.sh中的刚才的那一段改为如下:

#produce MFCC features 生成 mfcc特征
rm -rf data/mfcc && mkdir -p data/mfcc &&  cp -R data/{train,dev,test,test_phone} data/mfcc || exit 1;
#先删除data下的mfcc目录, 并且创建mfcc目录,拷贝这些数据到mfcc目录
for x in train; do      这里改为去掉原来的dev和test目录
   #make  mfcc   生成mfcc
   steps/make_mfcc.sh --nj $n --cmd "$train_cmd" data/mfcc/$x exp/make_mfcc/$x mfcc/$x || exit 1; 
   #compute cmvn 计算cmvn
   #steps/compute_cmvn_stats.sh data/mfcc/$x exp/mfcc_cmvn/$x mfcc/$x || exit 1;      这里先注释掉

done

我们的意思是先跑一下make_mfcc.sh看看它干了什么,

我们先执行一下看看,

我们看到调用了make_mfcc.sh,并且显示了两次come here,for test,第一次是run.sh最开始调用的时候调用的path.sh,第二次是这个for循环调用的,但是在调用的过程中显示
basename: missing operand

Try 'basename --help' for more information.   
说明basename并没有找到,我们一会找找原因,这里面基本上确定在make_mfcc.sh的第19行,我们看了一下第19行发现是我们的注释,所以分析是由于我们的注释引起的,后面我们在分析。

我们现在将一步一步的打开刚才注释的脚本,我们打开刚才注释的. parse_options.sh || exit 1;
if [ -f path.sh ]; then . ./path.sh; fi
. parse_options.sh || exit 1; 
#应该是解析命令行选项,其实调用的是utils/parse_options.sh,对于这个文件的研究我们放到后面。
我们往后看
if [ $# -lt 1 ] || [ $# -gt 3 ]; then
   echo "Usage: $0 [options] <data-dir> [<log-dir> [<mfcc-dir>] ]";
   echo "e.g.: $0 data/train exp/make_mfcc/train mfcc"
   echo "Note: <log-dir> defaults to <data-dir>/log, and <mfccdir> defaults to <data-dir>/data"
   echo "Options: "
   echo "  --mfcc-config <config-file>                      # config passed to compute-mfcc-feats "
   echo "  --nj <nj>                                        # number of parallel jobs"
   echo "  --cmd (utils/run.pl|utils/queue.pl <queue opts>) # how to run jobs."
   echo "  --write-utt2num-frames <true|false>     # If true, write utt2num_frames file."
   exit 1;
fi

这块就是如果不符合这些参数列表,则需要更改符合这些参数。

这里说明一下shell的参数具体是这样的:
$# 表示提供到shell脚本或者函数的参数总数;
$1 表示第一个参数。
-ne 表示 不等于
另外:
整数比较
-eq 等于,如:if ["$a" -eq "$b" ]
-ne 不等于,如:if ["$a" -ne "$b" ]
-gt 大于,如:if ["$a" -gt "$b" ]
-ge大于等于,如:if ["$a" -ge "$b" ]

-lt 小于,如:if ["$a" -lt "$b" ]
-le 小于等于,如:if ["$a" -le "$b" ]
< 小于(需要双括号),如:(("$a" < "$b"))
<= 小于等于(需要双括号),如:(("$a" <= "$b"))
‘>‘ 大于(需要双括号),如:(("$a" "$b"))
‘>=’ 大于等于(需要双括号),如:(("$a" >= "$b"))
另外:$?是shell变量,表示"最后一次执行命令"的退出状态.0为成功,非0为失败.

我们接着看后面的内容

data=$1   #将第一个参数赋给data,其实就是上面这个说明中说的<data-dir>
if [ $# -ge 2 ]; then    #判断如果参数总数大于等于2,则将第二个参数赋给logdir,否则将data下的log路径赋给logdir
  logdir=$2
else
  logdir=$data/log
fi
if [ $# -ge 3 ]; then #判断如果参数总数大于等于3,则将第三个参数赋给mfccdir,否则将data下的data路径赋给mfccdir
  mfccdir=$3
else
  mfccdir=$data/data
fi
我们接着看后面的内容
# make $mfccdir an absolute pathname.  这块是调用perl脚本来创建目录,给mfccdir赋值一个绝对路径名称
mfccdir=`perl -e '($dir,$pwd)= @ARGV; if($dir!~m:^/:) { $dir = "$pwd/$dir"; } print $dir; ' $mfccdir ${PWD}`
这里是perl脚本

@ARGV首先是一个数组,不管脚本里有没有把它写出来,它始终是存在的。@ARGV是Perl默认用来接收参数的数组,这些参数来源于用户在命令行上输入的参数。

如以下例子:

(1)命令行上输入:

perl xx.pl  C:/msConvert.exe  C:/in  C:/out

解释:命令行上输入了三个参数,即C:/msConvert.exe,C:/in和C:/out,这个三个参数是一个程序路径和两个文件夹路径。当用户在命令行上输入这三个参数时,perl已经将它们储存在@ARGV这个数组里了。也就是,@ARGV[0]是C:/msConvert.exe,@ARGV[1]是C:/in,@ARGV[2]是C:/out。

即@ARGV=qw(C:/msConvert.exe C:/in C:/out)

xx.pl脚本里有:

my ($msConvert,$inDir,$outDir)=@ARGV[0,1,2];

解释:这是建立了三个标量,即$msConvert,$inDir,$outDir;然后对这三个标量进行了赋值,即将C:/msConvert.exe赋值给$msConvert,C:/in赋值给$inDir,C:/out赋值给$outDir。

我们接着往下看:
# use "name" as part of name of the archive. 使用“名称”作为文件名称的一部分

name=`basename $data`

mkdir -p $mfccdir || exit 1;  #创建两个目录

mkdir -p $logdir || exit 1; #创建两个目录

我们接着往下看:
if [ -f $data/feats.scp ]; then   
#我们现在没有分析出$data是哪个目录,我们在之前加入一个打印看一下
  mkdir -p $data/.backup
  echo "$0: moving $data/feats.scp to $data/.backup"
  mv $data/feats.scp $data/.backup
fi
我们在这段之前加入一个打印,并且注释掉后面的
echo "----test: $data"    #我们运行试一下
   

我们发现$data就是data/mfcc/train  因为是run.sh中的for 遍历train/dev/test目录,所以本次是 data/mfcc/train ,所以我们接着打开注释掉的代码,分析
if [ -f $data/feats.scp ]; then   #这里就是判断如果data/mfcc/train下存在feats.scp,就将创建一个备份的目录,将feats.scp移动到备份的目录中去,但是其实咱们并没有这个文件,所以此处并不调用
  mkdir -p $data/.backup
  echo "$0: moving $data/feats.scp to $data/.backup"
  mv $data/feats.scp $data/.backup
fi

我们接着往下看:

scp=$data/wav.scp    #将data/mfcc/train下的wav.scp赋值给scp
#required="$scp $mfcc_config" #像刚才一样,我们并不太清楚$mfcc_config是什么我们在之前加入一个打印看一下,我们注释掉这句以及后面的

echo "----test: $mfcc_config"    #我们运行试一下

我们看到$mfcc_config就是conf/mfcc.conf,所以required="$scp $mfcc_config"就相当于将“$data/wav.scp  conf/mfcc.conf”赋给required,我们
打开刚才注释的,继续往下看。
for f in $required; do
  if [ ! -f $f ]; then
    echo "make_mfcc.sh: no such file $f"
    exit 1;
  fi
done                   
#这里其实就是判断是否有足够的文件,才继续往下运行
utils/validate_data_dir.sh --no-text --no-feats $data || exit 1;   #这里调用了一个校验数据目录的脚本,这里会调用一些脚本来检测各种文件及目录是否存在等等,我们后面在分析。
我们继续往下看:
if [ -f $data/spk2warp ]; then
  echo "$0 [info]: using VTLN warp factors from $data/spk2warp"
  vtln_opts="--vtln-map=ark:$data/spk2warp --utt2spk=ark:$data/utt2spk"
elif [ -f $data/utt2warp ]; then
  echo "$0 [info]: using VTLN warp factors from $data/utt2warp"
  vtln_opts="--vtln-map=ark:$data/utt2warp"
fi               #这块是是否通过VTLN(特征级声道长度标准化),这里没有这两种文件,所以这里不调用

特征级声道长度标准化(归一化):计算-mfcc-feat和计算-plp-feat程序接受VTLN 扭曲因子选项。
在当前脚本中,这仅用于初始化线性版本的VTLN的线性变换。VTLN的作用是移动三角形频率箱的中心频率的位置。
VTLN通过移动三角频率区的中心频率的位置来起作用。 移动频率区间的翘曲函数是频率空间中的分段线性函数。 要了解它,请记住以下数量:
0 <= low-freq <= vtln-low < vtln-high < high-freq <= nyquist
这里,低频和高频是标准MFCC或PLP计算中使用的最低和最高频率(丢弃较低和较高频率)。 vtln-low和vtln-high是VTLN中使用的频率截止,它们的功能是确保所有的mel箱都有合理的宽度。
我们实现的VTLN变形函数是一个分段线性函数,有三个段将区间[low-freq,high-freq]映射到[low-freq,high-freq]。令翘曲函数为W(f),其中f为频率。中心段将f映射到f / scale,其中“scale”是VTLN 扭曲因子(通常在0.8到1.2的范围内)。
下段连接中段的x轴上的点是定义的点f,使得min(f,W(f))= vtln-low。中间段连接上段的x轴上的点是定义的点f,使得max(f,W(f))= vtln-high。下段和上段的斜率和偏移由连续性和W(低频率)=低频率和W(高频率)=高频率的要求决定。这种扭曲功能与HTK不同;在HTK版本中,“vtln-low”和“vtln-high”数量被解释为x轴上发生不连续的点,这意味着必须选择“vtln-high”变量仔细地根据可能的扭曲因子范围的知识(否则可能出现具有空尺寸的梅尔箱)。
合理的设置如下(对于16kHz采样语音); 请注意,这反映了我们对合理值的理解,并不是任何非常仔细的调整实验的产物。


echo "----test: $mfccdir"    #为了配合后面的工作,我们查看一下$mfccdir,注释掉后面的脚本,运行一下
for n in $(seq $nj); do     #for循环,之前nj=4
  # the next command does nothing unless $mfccdir/storage/ exists, see
  # utils/create_data_link.pl for more info.   #除非$mfccdir/storage/存在,否则下一个命令不会执行任何操作,请参见utils/create_data_link.pl更多信息。

  utils/create_data_link.pl $mfccdir/raw_mfcc_$name.$n.ark
done

#我们运行后发现$mfccdir就是/opt/kaldi/egs/thchs30/s5/mfcc/train,我们查看后,发现没有$mfccdir/storage/所以上面没有执行。
我们接着往下看

if $write_utt2num_frames; then  #这个在make_mfcc.sh开始时声明为false,所以这里write_num_frames_opt=    不赋值
  write_num_frames_opt="--write-num-frames=ark,t:$logdir/utt2num_frames.JOB"
else
  write_num_frames_opt=

fi
未完待续。。。。。。

猜你喜欢

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