zoukankan      html  css  js  c++  java
  • [转]Kaldi命令词识别

    转自:

    http://www.jianshu.com/p/5b19605792ab?utm_campaign=maleskine&utm_content=note&utm_medium=pc_all_hots&utm_source=recommendation

    http://www.jianshu.com/p/6338fab6bd0a

    刚刚拿到一个简单语料库练手,发现只有语音和对应文字, 这篇文章记录了从数据预处理到kaldi对数据进行训练和测试的全过程,这里首先训练单音节模型,其他模型后面再补充。
    
    语料库处理
    
    task 0: 观察语料库
    
    语料库主要用于命令词识别,包括200个词汇,2000条语音,10个说话者分别对200个词汇进行录音。语音目录以说话者id标识:
    
    $ tree -d
    ├── speaker001
    ├── speaker002
    ├── speaker003
    ├── speaker004
    ├── speaker005
    ├── speaker006
    ├── speaker007
    ├── speaker008
    ├── speaker009
    ├── speaker010
    └── Levoice.list
    每个说话者文件夹目录下包含对应的200条语音:
    
    └┤ tree speaker001
    speaker001
    ├── 00001.wav
    ├── 00002.wav
    ├── 00003.wav
    ...
    └── 00200.wav
    语音文字说明文件Levoice.list 格式为<语音id> <文字> <录音时长>,例如:
    
    └┤ head -n 2 Levoice.list 
    speaker001/00001.wav    三六零通讯录    5.6
    speaker001/00002.wav    三六五日历    2.8
    语料库所给的资源应用到kaldi还需要汉字发音词典,这里只能自己准备,下面会参考thchs30语料库的词典准备自己的词典。
    
    task 1: 预处理语料库
    
    为方便后续操作,需要对语料库文件进行预处理,这部分包括:
    
    重新重命名语音文件,使2000个语音文件具有唯一标识(speakerid_voiceid.wav)
    划分训练、测试、验证数据集
    根据Levoice.list生成utt2words.txt ,进行文件名对应汉字映射。
    上述过程脚本(注意rname命令在Ubuntu和Centos中有细微差别):
    
     !/bin/bash
    
    #if need cv or not
    needcv=true
    
    # rename wav files by add prefix by "speaker"
    start_path=`pwd`
    for dirname in $(ls | grep "speaker")
    do
        #get first filename
        filename=$(ls $dirname | head -n 1)
        if [[ $filename =~ "speaker" ]]; then
            echo "files in $dirname have already renamed, passing..."
        else
            echo "now rename flies with prefix speakers"
            echo $dirname
            cd $dirname
            #in centos rename
            rename "00" $dirname"_00" "00"*
            # ubuntu using follows
            #rename "s/00/$dirname""_00/" 00*
            cd ..
        fi
    done
    
    # devide file to train, cv and test
    cd $start_path
    rm -rf  test train cv  && mkdir test train cv
    
    i=1
    for dirname in $(ls | grep "speaker")
    do
        if [ $i -lt 9 ];then
            cp $dirname/* train
        else
            cp $dirname/* test
        fi
        let i=$i+1
    
    done
    
    function rand(){
        min=$1
        max=$(($2-$min+1))
        num=$(($RANDOM+1000000000))
        echo $(($num%$max+$min))  
    }
    
    count=0
    array=("0" "0" "0" "0")
    #ls -al train
    if [ needcv ]; then
        for file in $(ls train | grep "speak")
        do
            array[$count]=$file
            let count=$count+1
            if [ $count -eq 4 ];then
                rnd=$(rand 0 3)
                mv train/${array[$rnd]} cv
                #echo ${array[$rnd]}
                let count=0
            fi
        done
        echo "cv files prepared over, examples number is $(ls cv | wc -l)"
    fi
    echo "train files number is $(ls train | wc -l)"
    echo "test files number is $(ls test | wc -l)"
    语料库对训练集、验证集、测试集参考thchs30,这里将说话人9、10语音作为测试集,再从1-8语音集中的1600百条语音文件四条语音为组随机选择一条语音归入验证集,剩下的作为训练集。划分结果训练集、验证集、测试集比例6:2:2。
    
    在语料库目录运行上脚本,会在该目录下产生trian、test和cv目录,这些目录及文件将被后面使用。
    
    最后直接将Levoice.list中的信息进行简单字符替换即可:
    
    speaker001/00001.wav    三六零通讯录    5.6
    ---->
    speaker001_00001.wav    三六零通讯录    5.6
    可以在vi或其他编辑器中替换即可。
    
    应用Kaldi
    
    task0 : 构建kaldi项目结构
    
    参照其他项目,首先复制创建项目结构目录,配置文件以及项目需要使用的依赖工具,这里多参考thchs30部分结构。在egs 目录下建立/wakeup/s5作为项目目录,在该目录下准备以下文件:
    
    $ tree -L 1
    |-- cmd.sh // 运行配置目录
    |-- conf  // 配置文件目录
    |-- local //存放run.sh 中调用的脚本工具,需要自己编写
    |-- path.sh //Kaldi 工具和库目录添加到PATH
    |-- run.sh // top层脚本,运行该脚本训练数据和测试, 需要自己编写
    |-- steps // kaldi 脚本工具, 复制到工程目录下
    |-- tools // kaldi 脚本工具, 复制到工程目录下
    `-- utils // kaldi 脚本工具, 复制到工程目录下
    这里cmd.sh里根据自己运行方式配置运行参数,这里配置成单机运行
    
    export train_cmd=run.pl
    export decode_cmd="run.pl --mem 4G"
    export mkgraph_cmd="run.pl --mem 8G"
    conf 目录包含一些配置文件,这里主要将系统采样频率与语料库的采样频率设置为一致:
    
    $ ls
    decode_dnn.config  fbank.conf  mfcc.conf
    $ more mfcc.conf 
    --use-energy=false   # only non-default option.
    --sample-frequency=8000
    $ more decode_dnn.config 
    beam=18.0 # beam for decoding.  Was 13.0 in the scripts.
    lattice_beam=10.0 # this has most effect on size of the lattices.
    $ more fbank.conf 
    --sample-frequency=8000
    --num-mel-bins=40
    task1 : 准备训练文件
    
    参照kaldi数据准备部分文档,该部分需要自己根据语料库分别就train,test,cross validation目录生成以下文件:
    
    text : < uttid > < word >
    wav.scp : < uttid > < utter_file_path >
    utt2spk : < uttid > < speakid >
    spk2utt : < speakid > < uttid >
    word.txt : 同 text
    编写local/data_pre.sh脚本供run.sh调用(下面会涉及run.sh脚本的编写),传入参数运行目录以及语料库目录:
    
    #!/bin/bash
    # 2017-3-23 by zqh 
    
    # This file prepares files needed in kaldi
    # including text, wav.scp, utt2spk, spk2utt
    # output: 
    #   data/train dir include infomation of train data
    #   data/test dir include infomation of test data
    #   data/cv dir include infomation of cross validation data
    
    run_dir=$1
    dataset_dir=$2
    
    cd $run_dir
    echo "prepare data in data/{train, test, cv}"
    mkdir -p data/{train,test,cv}
    
    #create text, wav.scp, utt2spk, spk2utt
    (
    i=0
    for dir in train cv test; do
        echo "clean dir data/$dir"
        cd $run_dir/data/$dir
        rm -rf wav.scp utt2spk spk2utt word.txt text  
        #phone.txt
        for data in $(find $dataset_dir/$dir/*.wav | sort -u | xargs -i basename {} .wav);do
            let i=$i+1
            spkid=$(echo $data | awk -F"_" '{print "" $1}')
            uttid=$data
            echo $uttid $dataset_dir/$dir/$data.wav >> wav.scp
            echo $uttid $spkid >> utt2spk
            # gen word.txt
            echo $uttid $(cat $dataset_dir/utt2word.txt | grep $uttid | awk '{print "" $2}') >> word.txt
            # gen phone.txt TODO
        done
        cp word.txt text
        sort wav.scp -o wav.scp
        sort utt2spk -o utt2spk
        sort text -o text
        # sort phone.txt -o phone.txt
    done
    echo "all file number is $i"
    ) || exit 1
    
    utils/utt2spk_to_spk2utt.pl data/train/utt2spk > data/train/spk2utt
    utils/utt2spk_to_spk2utt.pl data/cv/utt2spk > data/cv/spk2utt
    utils/utt2spk_to_spk2utt.pl data/test/utt2spk > data/test/spk2utt
    task2 : 训练语言模型
    
    由于这里仅仅需要对语料库中的200个命令词进行识别,大而全的汉语词典并不必要,这里需要根据自己的语料建立词典并且生成语言模型。
    
    task 2.1 : 准备词典
    
    根据kaldi的要求,需要准备的词典包括以下文件(我这里和语料库放在同个目录下,后面kaldi从该目录下读取):
    
    [username@hostname dict]$ pwd
    /home/username/dataset_wakeup/resource/dict
    [username@hostname dict]$ ls
    extra_questions.txt  lexiconp.txt  lexicon.txt  nonsilence_phones.txt  optional_silence.txt  silence_phones.txt
    对上面文件简单说明:
    
    lexicon.txt: 词典,包括语料中涉及的词汇与发音,与单字及其发音。
    silence_phones.txt:静音标识,这里为sil。
    nonsilence_phones.txt : 非静音标识,与silence_phones.txt共同组成lexicon.txt中的发音。
    extra_questions.txt : 包含重音音调标记,这里没有用到
    lexiconp.txt : 如果一个词有不同发音,则会在不同行中出现多次。如果你想使用发音概率,你需要建立 exiconp.txt 而不是 lexicon.txt,这里未使用
    以上文件可以参考复制thchs30的resource资源,只要替换lexicon.txt为自己的字典,并且追加thchs30中lexicon.txt中所有的单字及其发音(简单awk命令即可)。此外该语料库仅仅提供了汉字无对应发音,需要自己参考thchs30中的词典准备,(心想只有200条,觉得手打的会很快,事实用了2-3个小时,心累,回头想可以写程序完成)。
    lexicon.txt 文件内容大致为:
    
    $ more lexicon.txt 
    SIL sil
    <SPOKEN_NOISE> sil
    三六零通讯录 s an1 l iu4 l ing2 t ong1 x vn4 l u4
    三六五日历 s an1 l iu4 uu u3 r iz4 l i4
    三D图库    s an1 d i4 t u2 k u4
    task 2.2: 生成语言模型
    
    语言模型训练需要使用n-gram算法,借助sirlm工具可以简单实现,并进行语言模型生成:
    
    安装
    
    下载sirlm安装包(官网下载速慢,也可通过在github上找到相应资源下载),解压后进入最上层目录进行安装。
    export SRILM=pwd
    make
    把$make_dir/bin/i686-m64/加入PATH以便使用其中脚本
    生成语言模型
    
    在语料库目录下创建lm_word文件夹(方便管理),复制上面的字典lexicon.txt,并删除前两行,保存为作为words.txt作语料输入文件进行n-gram语言模型生成(由于只是词汇识别设置n=1):
    
    ngram-count -order 1 -text words.txt -lm word.arpa
    其他参数可以参考:
    
    -order  指定n-gram的n是多少,默认是3
    -text   提供输入的语料文件,统计该语料中的n-gram
    -lm     指定输出的lm文件
    -vocab  用来指定对哪些词进行n-gram统计
    -wbdiscount1 表示1gram Witten-Bell discounting 
    Note:参数顺序无所谓
    该命令生成arpa格式的语言模型文件,后面由kaldi的其他工具转换为FST格式使用。
    
    完成语言模型的生成后,对应的可以在run.sh脚本中利用该部分的语言模型,通过kaldi提供的工具构建语言模型的FST格式文件,这部分 主要创建了data/{dict,lang,graph}目录及相应文件,并在后面的构建解码图的过程中使用。run.sh脚本该部分代码:
    
    #gen lang dir 
    (
        echo "create new dir data/dict,lang,graph"
        cd $run_path
        mkdir -p data/{dict,lang,graph} && 
        cp $dataset//resource/dict/{extra_questions.txt,nonsilence_phones.txt,optional_silence.txt,silence_phones.txt} data/dict && 
        cat $dataset/resource/dict/lexicon.txt | 
        grep -v '<s>' | grep -v '</s>' | sort -u > data/dict/lexicon.txt || exit 1;
        utils/prepare_lang.sh --position_dependent_phones false data/dict "<SPOKEN_NOISE>" data/local/lang data/lang || exit 1;
        gzip -c $dataset/King-ASR-M-005/lm_word/word.arpa > data/graph/word.arpa.gz || exit 1;
        utils/format_lm.sh data/lang data/graph/word.arpa.gz $dataset/King-ASR-M-005/lm_word/lexicon.txt data/graph/lang || exit 1;
    )
    这里主要包括utils/prepare_lang.sh 、 和utils/format_lm.sh 两个脚本的调用,不作具体分析。
    
    作者:zqh_zy
    链接:http://www.jianshu.com/p/5b19605792ab
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    task4 : 特征提取(FMCC)
    
    完成了语言模型的构建,下面开始生成声学模型部分,首先对语音文件进行特征提取,这里用到了上面准备的文件,包括:text, wav.scp, utt2spk, spk2utt 。
    run.sh中完成特征提取,并对语音进行归一化处理:
    
    #gen MFCC features
    rm -rf data/mfcc && mkdir -p data/mfcc &&  cp -R data/{train,cv,test} data/mfcc || exit 1;
    for x in train cv test; do
       #make  mfcc 
       steps/make_mfcc.sh --nj $n --cmd "$train_cmd" data/mfcc/$x exp/make_mfcc/$x mfcc/$x || exit 1;
       #compute cmvn
       steps/compute_cmvn_stats.sh data/mfcc/$x exp/mfcc_cmvn/$x mfcc/$x || exit 1;
    done
    生成的特征提取相关文件保存在data/mfcc目录下,真实的数据保存在mfcc/目录下。
    
    task5 : 训练声学模型、 构建解码图
    
    该部分调用kaldi脚本,训练单音节模型,后面测试证明,单个词汇的识别,该模型同样能保证良好的识别效果,同样run.sh脚本中:
    
    #monophone
    #steps/train_mono.sh --boost-silence 1.25 --nj $n --cmd "$train_cmd" data/mfcc/train data/lang exp/mono || exit 1;
    声学模型的训练结果文件保存在exp/mono目录下。下面构建解码图,这部分调用utils/mkgraph.sh, 利用先前创建的语言模型和上步训练的声学模型构建HCLG解码图,该部分生成的解码图保存在exp/mono/graph_word文件夹下:
    
    utils/mkgraph.sh --mono --nj $n  data/graph/lang exp/mono exp/mono/graph_word  || exit 1;
    task6: 测试
    
    在local目录下创建data_decode.sh 脚本对解码步骤进行封装:
    
    #!/bin/bash
    #decoding wrapper
    #run from ../
    nj=2
    mono=false
    . ./cmd.sh ## You'll want to change cmd.sh to something that will work on your system.
    . ./path.sh ## Source the tools/utils (import the queue.pl)
    . utils/parse_options.sh || exit 1;
    
    decoder=$1
    srcdir=$2
    datadir=$3
    
    if [ $mono = true ];then
      echo  "using monophone to generate graph"
      opt="--mono"
    fi
    
    #decode word
    $decoder --cmd "$decode_cmd"  $srcdir/graph_word $datadir/test $srcdir/decode_test_word || exit 1
    在run.sh脚本中调用上脚本:
    
    #test mono model
    local/data_decode.sh --nj 2 "steps/decode.sh" exp/mono data/mfcc &
    这里注意由于测试集只有两个说话者,并发度设置为2,否则会出现文件分割数与并发数不匹配的情况,解码过程主要用到特征提取后的test文件,上部分生成的解码图,测试结果在exp/mono/decode_test_word文件夹中查看。
    
    为了对测试结果进行评估,还需在local目录下完成打分脚本相关的代码,这里参考thchs30,拷贝文件:score.sh、wer_output_filter 。
    
    下面给出完整的run.sh脚本,之后运行脚本:
    
    #!/bin/bash
    
    . ./cmd.sh
    . ./path.sh
    
    run_path=`pwd`
    n=8 #parallel jobs
    
    #dataset path
    dataset=~/dataset_wakeup
    
    #data prepare
    #gen text, wav.scp, utt2spk, spk2utt
    local/data_prep.sh $run_path $dataset/King-ASR-M-005 || exit 1
    
    
    #gen lang dir 
    (
        echo "create new dir data/dict,lang,graph"
        cd $run_path
        mkdir -p data/{dict,lang,graph} && 
        cp $dataset//resource/dict/{extra_questions.txt,nonsilence_phones.txt,optional_silence.txt,silence_phones.txt} data/dict && 
        cat $dataset/resource/dict/lexicon.txt | 
        grep -v '<s>' | grep -v '</s>' | sort -u > data/dict/lexicon.txt || exit 1;
        utils/prepare_lang.sh --position_dependent_phones false data/dict "<SPOKEN_NOISE>" data/local/lang data/lang || exit 1;
        gzip -c $dataset/King-ASR-M-005/lm_word/word.arpa > data/graph/word.arpa.gz || exit 1;
        utils/format_lm.sh data/lang data/graph/word.arpa.gz $dataset/King-ASR-M-005/lm_word/lexicon.txt data/graph/lang || exit 1;
    )
    
    #gen MFCC features
    rm -rf data/mfcc && mkdir -p data/mfcc &&  cp -R data/{train,cv,test} data/mfcc || exit 1;
    for x in train cv test; do
       #make  mfcc 
       steps/make_mfcc.sh --nj $n --cmd "$train_cmd" data/mfcc/$x exp/make_mfcc/$x mfcc/$x || exit 1;
       #compute cmvn
       steps/compute_cmvn_stats.sh data/mfcc/$x exp/mfcc_cmvn/$x mfcc/$x || exit 1;
    done
    
    #monophone
    steps/train_mono.sh --boost-silence 1.25 --nj $n --cmd "$train_cmd" data/mfcc/train data/lang exp/mono || exit 1;
    #decode word
    
    # make decoder graph
    utils/mkgraph.sh --mono  data/graph/lang exp/mono exp/mono/graph_word  || exit 1;
    
    #test mono model
    local/data_decode.sh --nj 2 "steps/decode.sh" exp/mono data/mfcc &
    运行脚本,由于数据量不大,并不需要很长时间,运行测试结束查看效果:
    
    [uesrname@hostname scoring_kaldi]$ ls
    best_wer  log  penalty_0.0  penalty_0.5  penalty_1.0  test_filt.txt  wer_details
    [uesrname@hostname scoring_kaldi]$ more best_wer 
    %WER 5.57 [ 100 / 1795, 19 ins, 4 del, 77 sub ] exp/mono/decode_test_word/wer_17_1.0
    错词率为5.57%,在penalty_1.0中可以查看最好的识别结果。
    
    小结
    
    文章记录了从拿到语料库,到应用Kaldi的全过程,主要想对流程进行总结,对语音识别相关的原理没有涉及太多。另外这里仅仅训练了单音节模型,其他模型可以参照thchs30完成,这里不再补充。
    过程中遇到的小问题很多,一个比较典型的,一开始想偷懒直接使用thchs30的词典,后来识别结果很差,单词均为一个或两个毫不相干的字。考虑自己语料库中的词汇在thchs30的词典中并未涉及,还是通过自己标注词典解决问题。
    
    作者:zqh_zy
    链接:http://www.jianshu.com/p/6338fab6bd0a
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    白帽子 攻防
    自定义WCF的配置文件
    .net快速创建PDF文档 by c#
    如何在面试中发现优秀程序员
    kafka-java客户端连接
    xshell 登陆堡垒机实现自动跳转
    良好的编程习惯
    Mycat-介绍
    scala-传名函数和传值函数
    springboot-31-springboot静态注入
  • 原文地址:https://www.cnblogs.com/welen/p/7567694.html
Copyright © 2011-2022 走看看