Kaldi声纹识别代码详解|egs/aishell
# stage0 数据准备
data=/export/a05/xna/data # 改为自己存放Data的位置
data_url=www.openslr.org/resources/33
. ./cmd.sh # 设置本地运行/群集
. ./path.sh # 设置需要的路径
set -e # exit on error
# 将$data_url下载到$data/data_aishell文件夹中
local/download_and_untar.sh $data $data_url data_aishell
local/download_and_untar.sh $data $data_url resource_aishell
# Data Preparation
local/aishell_data_prep.sh $data/data_aishell/wav $data/data_aishell/transcript
2
3
4
5
6
7
8
9
10
11
12
13
14
set -e
参考链接:shell脚本中 “set -e” 的作用 (opens new window)
你写的每个脚本都应该在文件开头加上
set -e
,这句语句告诉bash如果任何语句的执行结果不是true则应该退出。这样的好处是防止错误像滚雪球般变大导致一个致命的错误,而这些错误本应该在之前就被处理掉。如果要增加可读性,可以使用set -o errexit
,它的作用与set -e
相同。
# local/download_and_untar.sh
remove_archive=false
# 没有运行这里
if [ "$1" == --remove-archive ]; then
remove_archive=true
shift
fi
# 没有运行这里
if [ $# -ne 3 ]; then
echo "Usage: $0 [--remove-archive] <data-base> <url-base> <corpus-part>"
echo "e.g.: $0 /export/a05/xna/data www.openslr.org/resources/33 data_aishell"
echo "With --remove-archive it will remove the archive after successfully un-tarring it."
echo "<corpus-part> can be one of: data_aishell, resource_aishell."
fi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$# -ne 3
传入的参数总数不等于3
data=$1
url=$2
part=$3
if [ ! -d "$data" ]; then
echo "$0: no such directory $data"
exit 1;
fi
part_ok=false
list="data_aishell resource_aishell"
for x in $list; do
if [ "$part" == $x ]; then part_ok=true; fi
done
# 没有运行这里
if ! $part_ok; then
echo "$0: expected <corpus-part> to be one of $list, but got '$part'"
exit 1;
fi
# 检查下载链接是否存在
if [ -z "$url" ]; then
echo "$0: empty URL base."
exit 1;
fi
if [ -f $data/$part/.complete ]; then
echo "$0: data part $part was already successfully extracted, nothing to do."
exit 0;
fi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
参数说明
-z
:测试指定字符是否为空,空着真,非空为假;-f
:测试文件是否为普通文件exit 0
exit命令用于退出当前shell,在shell脚本中可以终止当前脚本执行,如果前面执行成功的话,以0值为返回值退出。
# sizes of the archive files in bytes.
sizes="15582913665 1246920"
# 已经提前下载好,已有tgz压缩文件
if [ -f $data/$part.tgz ]; then
size=$(/bin/ls -l $data/$part.tgz | awk '{print $5}')
size_ok=false
# 检查是否下载完整
for s in $sizes; do if [ $s == $size ]; then size_ok=true; fi; done
if ! $size_ok; then
echo "$0: removing existing file $data/$part.tgz because its size in bytes $size"
echo "does not equal the size of one of the archives."
rm $data/$part.gz
else
echo "$data/$part.tgz exists and appears to be complete."
fi
fi
if [ ! -f $data/$part.tgz ]; then
if ! which wget >/dev/null; then
echo "$0: wget is not installed."
exit 1;
fi
full_url=$url/$part.tgz
echo "$0: downloading data from $full_url. This may take some time, please be patient."
cd $data
if ! wget --no-check-certificate $full_url; then
echo "$0: error executing wget $full_url"
exit 1;
fi
fi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
size=$(/bin/ls -l $data/$part.tgz | awk '{print $5}')
/bin/ls -l
:使用较长格式列出信息-rwxrwxrwx 1 zwj zwj 15582913665 4月 1 2019 /media/zwj/数据集/kaldi/data_aishell.tgz
1awk '{print $5}'
:输出第五个域的信息(字节数)15582913665
1
cd $data
# 看到这里--------------------------------------------------------------------------------------
if ! tar -xvzf $part.tgz; then
echo "$0: error un-tarring archive $data/$part.tgz"
exit 1;
fi
touch $data/$part/.complete
if [ $part == "data_aishell" ]; then
cd $data/$part/wav
for wav in ./*.tar.gz; do
echo "Extracting wav from $wav"
tar -zxf $wav && rm $wav
done
fi
echo "$0: Successfully downloaded and un-tarred $data/$part.tgz"
if $remove_archive; then
echo "$0: removing $data/$part.tgz file since --remove-archive option was supplied."
rm $data/$part.tgz
fi
exit 0;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# local/aishell_data_prep.sh
- 执行local/aishell_data_prep.sh后
只显示到2级目录:
data_aishell ├── transcript │ └── aishell_transcript_v0.8.txt └── wav ├── dev ├── test └── train其他
其中,dev/test/train文件结构一致,内有以spkID命名的文件夹,其中存放对应speaker的wav文件。
# stage1 提取MFCC
mfccdir=mfcc
for x in train test; do
# $train_cmd即为cmd.sh内设置
steps/make_mfcc.sh --cmd "$train_cmd" --nj 10 data/$x exp/make_mfcc/$x $mfccdir
sid/compute_vad_decision.sh --nj 10 --cmd "$train_cmd" data/$x exp/make_mfcc/$x $mfccdir
utils/fix_data_dir.sh data/$x
done
2
3
4
5
6
7
# steps/make_mfcc.sh
这是软链接,链接到/kaldi/egs/wsj/s5/steps
# sid/compute_vad_decision.sh
# utils/fix_data_dir.sh
# stage2 训练UBM
# train diag ubm
sid/train_diag_ubm.sh --nj 10 --cmd "$train_cmd" --num-threads 16 \
data/train 1024 exp/diag_ubm_1024
#train full ubm
sid/train_full_ubm.sh --nj 10 --cmd "$train_cmd" data/train \
exp/diag_ubm_1024 exp/full_ubm_1024
2
3
4
5
6
7
nj
,num-threads
的含义
# sid/train_diag_ubm.sh
# sid/train_full_ubm.sh
# stage3 训练ivector提取器
sid/train_ivector_extractor.sh --cmd "$train_cmd --mem 10G" \
--num-iters 5 exp/full_ubm_1024/final.ubm data/train \
exp/extractor_1024
2
3
# sid/train_ivector_extractor.sh
# stage4 提取ivector
sid/extract_ivectors.sh --cmd "$train_cmd" --nj 10 \
exp/extractor_1024 data/train exp/ivector_train_1024
2
# sid/extract_ivectors.sh
# stage5 训练PLDA模型
#train plda
$train_cmd exp/ivector_train_1024/log/plda.log \
ivector-compute-plda ark:data/train/spk2utt \
'ark:ivector-normalize-length scp:exp/ivector_train_1024/ivector.scp ark:- |' \
exp/ivector_train_1024/plda
2
3
4
5
# ivector-compute-plda
# ivector-normalize-length
# stage6 划分注册与测试集
#split the test to enroll and eval
mkdir -p data/test/enroll data/test/eval
cp data/test/{spk2utt,feats.scp,vad.scp} data/test/enroll
cp data/test/{spk2utt,feats.scp,vad.scp} data/test/eval
local/split_data_enroll_eval.py data/test/utt2spk data/test/enroll/utt2spk data/test/eval/utt2spk
trials=data/test/aishell_speaker_ver.lst
local/produce_trials.py data/test/eval/utt2spk $trials
utils/fix_data_dir.sh data/test/enroll
utils/fix_data_dir.sh data/test/eval
2
3
4
5
6
7
8
9
# local/split_data_enroll_eval.py
# local/produce_trials.py
# stage7 提取ivector
#extract enroll ivector
sid/extract_ivectors.sh --cmd "$train_cmd" --nj 10 \
exp/extractor_1024 data/test/enroll exp/ivector_enroll_1024
#extract eval ivector
sid/extract_ivectors.sh --cmd "$train_cmd" --nj 10 \
exp/extractor_1024 data/test/eval exp/ivector_eval_1024
2
3
4
5
6
# sid/extract_ivectors.sh
# stage8 打分并计算EER
#compute plda score
$train_cmd exp/ivector_eval_1024/log/plda_score.log \
ivector-plda-scoring --num-utts=ark:exp/ivector_enroll_1024/num_utts.ark \
exp/ivector_train_1024/plda \
ark:exp/ivector_enroll_1024/spk_ivector.ark \
"ark:ivector-normalize-length scp:exp/ivector_eval_1024/ivector.scp ark:- |" \
"cat '$trials' | awk '{print \\\$2, \\\$1}' |" exp/trials_out
#compute eer
awk '{print $3}' exp/trials_out | paste - $trials | awk '{print $1, $4}' | compute-eer -
2
3
4
5
6
7
8
9
10
# ivector-plda-scoring
# compute-eer
# shell命令整理
参考链接:kaldi中文语音识别_基于thchs30(4) - dqxiaoxiao的博客 - CSDN博客 (opens new window)
# exit
同于退出shell,并返回给定值。在shell脚本中可以终止当前脚本执行。执行exit可使shell以指定的状态值退出。若不设置状态值参数,则shell以预设值退出。状态值0代表执行成功,其他值代表执行失败。
检查上一命令脚本的退出码
./mycommand.sh EXCODE=$? if [ "$EXCODE" == "0" ]; then echo "O.K" fi
1
2
3
4
5一些约定
0表示成功(Zero - Success) 非0表示失败(Non-Zero - Failure) 2表示用法不当(Incorrect Usage) 127表示命令没有找到(Command Not Found) 126表示不是可执行的(Not an executable) >=128 信号产生
在脚本中,进入脚本所在目录,否则退出
cd $(dirname $0) || exit 1
1
# awk
参考链接:linux awk命令详解 (opens new window)
awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。
awk '{pattern + action}' {filenames}
pattern
表示 AWK 在数据中查找的内容,而 action
是在找到匹配内容时所执行的一系列命令。花括号({})不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。pattern
就是要表示的正则表达式,用斜杠括起来。
# 工作流程
读入有'\n'换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0则表示所有域,$1表示第一个域,$n表示第n个域。默认域分隔符是"空白键" 或 "[tab]键",所以$1表示登录用户,$3表示登录用户ip,以此类推。
[root@www ~]# last -n 5 <==仅取出前五行
root pts/1 192.168.1.100 Tue Feb 10 11:21 still logged in
root pts/1 192.168.1.100 Tue Feb 10 00:46 - 02:28 (01:41)
root pts/1 192.168.1.100 Mon Feb 9 11:41 - 18:30 (06:48)
dmtsai pts/1 192.168.1.100 Mon Feb 9 11:41 - 11:41 (00:00)
root tty1 Fri Sep 5 14:09 - 14:10 (00:01)
2
3
4
5
6
# 示例
改变域分隔符
awk -F ':' '{print $1}'
1同时输出多个域
awk -F ':' '{print $1"\t"$7}'
1账户与shell之间以逗号分割,而且在所有行添加列名name,shell,在最后一行添加"blue,/bin/nosh"
awk -F ':' 'BEGIN {print "name,shell"} {print $1","$7} END {print "blue,/bin/nosh"}'
1先执行BEGING,然后读取文件,读入有/n换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0则表示所有域,$1表示第一个域,$n表示第n个域,随后开始执行模式所对应的动作action。接着开始读入第二条记录······直到所有的记录都读完,最后执行END操作。
搜索/etc/passwd有root关键字的所有行
awk -F: '/root/' /etc/passwd
1
# 内置变量
ARGC 命令行参数个数
ARGV 命令行参数排列
ENVIRON 支持队列中系统环境变量的使用
FILENAME awk浏览的文件名
FNR 浏览文件的记录数
FS 设置输入域分隔符,等价于命令行 -F选项
NF 浏览记录的域的个数
NR 已读的记录数
OFS 输出域分隔符
ORS 输出记录分隔符
RS 控制记录分隔符
2
3
4
5
6
7
8
9
10
11
统计/etc/passwd:文件名,每行的行号,每行的列数,对应的完整行内容
awk -F ':' '{print "filename:" FILENAME ",linenumber:" NR ",columns:" NF ",linecontent:"$0}' /etc/passwd
使用printf替代print,可以让代码更加简洁,易读
awk -F ':' '{printf("filename:%10s,linenumber:%s,columns:%s,linecontent:%s\n",FILENAME,NR,NF,$0)}' /etc/passwd
print与printf的区别
print函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已。
printf函数,其用法和c语言中printf基本相似,可以格式化字符串,输出复杂时,printf更加好用,代码更易懂。
# awk编程
变量和赋值
统计/etc/passwd的账户人数:
awk '{count++;print $0;} END{print "user count is ", count}' /etc/passwd root:x:0:0:root:/root:/bin/bash ...... user count is 40
1
2
3
4变量初始化是0,但最好先做初始化:
awk 'BEGIN {count=0;print "[start]user count is ", count} {count=count+1;print $0;} END{print "[end]user count is ", count}' /etc/passwd [start]user count is 0 root:x:0:0:root:/root:/bin/bash ... [end]user count is 40
1
2
3
4
5统计某个文件夹下的文件占用的字节数,统计不包括文件夹的子目录:
ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size}' [end]size is 8657198
1
2字节数以M为单位:
ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size/1024/1024,"M"}' [end]size is 8.25889 M
1
2条件语句
if (expression) { # 单分支 statement; statement; ... ... } if (expression) { # 双分支 statement; } else { statement2; } if (expression) { # 多分支 statement1; } else if (expression1) { statement2; } else { statement3; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19统计某个文件夹下的文件占用的字节数,过滤4096大小的文件(一般都是文件夹):
ls -l |awk 'BEGIN {size=0;print "[start]size is ", size} {if($5!=4096){size=size+$5;}} END{print "[end]size is ", size/1024/1024,"M"}' [end]size is 8.22339 M
1
2循环语句
支持while、do/while、for、break、continue,这些关键字的语义和C语言中的语义完全相同
数组
awk中数组的下标可以是数字和字母,数组的下标通常被称为关键字(key)。值和关键字都存储在内部的一张针对key/value应用hash的表格里。由于hash不是顺序存储,因此在显示数组内容时会发现,它们并不是按照你预料的顺序显示出来的。数组和变量一样,都是在使用时自动创建的,awk也同样会自动判断其存储的是数字还是字符串。一般而言,awk中的数组用来从记录中收集信息,可以用于计算总和、统计单词以及跟踪模板被匹配的次数等等。
显示/etc/passwd的账户:
awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;}; END{for (i = 0; i < NR; i++) print i, name[i]}' /etc/passwd 0 root 1 daemon 2 bin 3 sys 4 sync 5 games ......
1
2
3
4
5
6
7
8
# 常用参数的含义
$0 就是当前shell脚本本身的名字;
$1 是你给你写的shell脚本传的第一个参数;
$2 是你给你写的shell脚本传的第二个参数;
**$# **表示提供到shell脚本或者函数的参数总数;
**$?**是shell变量,表示"最后一次执行命令"的退出状态,0为成功,非0为失败。
# ./与. ./的区别
参考链接:linux里面的命令:./和. /(这里有空格)的区别 (opens new window)
./
: 当前目录(相对路径的写法)
. /
:有空格的点号等同于source
命令,表示在当前shell环境执行后面的命令或脚本(不加点号默认是新开一个shell执行的)
例如 :/home/test.sh
等同于 source /home/test.sh
。
source
:用source执行的脚本所做的任何改变会影响当前shell(如,其中如果用cd
改变了目录,脚本执行完毕后,shell的目录就变了);如果没用source,就不会影响当前shell。
# 常用运算符
参考链接:shell 中 && || () {} 用法 (opens new window)
逻辑与:&&
&&左边的命令(命令1)返回真(即返回0,成功被执行)后,&&右边的命令(命令2)才能够被执行;换句话说,“如果这个命令执行成功&&那么执行这个命令”。
command1 && command2 && command3 ...
1逻辑或:||
||则与&&相反,如果||左边的命令(command1)未执行成功,那么就执行||右边的命令(command2);或者换句话说,“如果这个命令执行失败了||那么就执行这个命令,实现短路逻辑或操作。
如果 dir目录不存在,将输出提示信息 fail :
ls dir &>/dev/null || echo "fail"
1如果 dir目录存在,将输出提示信息 success,否则输出fail:
ls dir &>/dev/null && echo "success" || echo "fail"
1常用||组合示例:
echo $BASH |grep -q 'bash' || { exec bash "$0" "$@" || exit 1; }
1系统调用exec是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。
当前shell组合执行:()
如果希望把几个命令合在一起执行,shell提供了两种方法。既可以在当前shell也可以在子shell中执行一组命令。
(command1;command2;command3....) 多个命令之间用;分隔
1() 表示在当前 shell 中将多个命令作为一个整体执行。需要注意的是,使用 () 括起来的命令在执行前面都不会切换当前工作目录,也就是说命令组合都是在当前工作目录下被执行的,尽管命令中有切换目录的命令。
如果目录dir不存在,则执行组合命令:
ls dir &>/dev/null || (cd /home/;ls -1h;echo "success")
1子shell组合执行:{}
相应的命令将在子shell而不是当前shell中作为一个整体被执行,只有在{}中所有命令的输出作为一个整体被重定向时,其中的命令才被放到子shell中执行,否则在当前shell执行。
{ command1;command2;command3… } 注意:在使用{}时,{}与命令之间必须使用一个空格
1在子shell中执行打印操作
A=1;echo $A;{ A=2; };echo $A # 得到1 2 A=1;echo $A;( A=2; );echo $A # 得到1 1
1
2管道符号:|
参考链接:[shell]shell 中| && || () {} 用法以及shell的逻辑与或非 (opens new window)
command 1 | command 2
1把第一个命令command 1执行的结果作为command2的输入传给command 2,列出当前目录中的文档(含size),并把输出送给sort命令作为输入,sort命令按数字递减的顺序把ls的输出排序。
ls -s | sort -nr
1-s表示file size,-n表示numeric-sort,-r表示reverse,反转
运算级优先级表
# if语句的使用
参考链接:shell 学习之if语句 (opens new window)
# 条件测试类型
整数测试、字符测试、文件测试;
# 条件测试的表达式
[ expression ]: 括号两端必须要有空格
[[ expression ]] :括号两端必须要有空格
test expression
组合测试条件:
-a
: and
-o
: or
!
: 非
# 整数测试
-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"))
# 字符串比较
==
等于 两边要有空格
!=
不等
>
大于
<
小于`
# 文件测试
-z string
:测试指定字符是否为空,空着真,非空为假;
-n string
:测试指定字符串是否为不空,空为假 非空为真;
-e file
:测试文件是否存在;
-f file
: 测试文件是否为普通文件;
-d file
:测试指定路径是否为目录;
-r file
:测试文件对当前用户是否可读;
-w file
:测试文件对当前用户是否可写;
-x file
:测试文件对当前用户是都可执行;
-z
:是否为空 为空则为真;
-a
:是否不空;
# if语法结构
- 单分支
if 判断条件;then
statement1
statement2
.......
fi
2
3
4
5
- 双分支
if 判断条件;then
statement1
statement2
.....
else
statement3
statement4
fi
2
3
4
5
6
7
8
注意点
if语句进行判断是否为空:
[ "$name” = "" ]
等同于[ ! "$name" ]
或[ -z "$name" ]
;使用if语句的时候进行判断如果是进行数值类的 ,建议使用
let(())
进行判断,(())
中变量是可以不使用$
来引用的;对于字符串等使用
test[ ]
or[[ ]]
进行判断;
# 条件测试的写法
- 执行一个命令的结果:
if grep -q "rm" fs.sh;then
- 传回一个命令执行结果的相反值 :
if !grep -q "rm" fs.sh;then
- 使用复合命令((算式)):
if ((a>b));then
- 使用bash关键字 [[判断式]]:
if [[ str > xyz ]];then
- 使用内置命令:test 判断式:
if test "str" \> "xyz";then
- 使用内置命令:[判断式] 类似test:
if [ "str" \> "xyz" ];then
- 使用-a -o进行逻辑组合:
[ -r filename -a -x filename ]
- 命令&&命令
- 命令||命令:
if grep -q "rm" fn.sh || [ $a -lt 100 ];then
# 待整理
中断程序而不关闭终端
参考链接:Linux在终端启动程序关闭终端不退出的方法 (opens new window)
nohup 命令 &
:如nohup ./studio.sh &
terminator的技巧
将terminator设为默认终端;
在别的窗口打开它后,按住红色窄条可以拖到已有的多终端集成窗口;
在空白处右键即可配置首选项,比如皮肤更换等;
查看文件夹树形结构
tree:
sudo apt-get install tree
常用格式:
tree [option] [directory]
常用参数[option]:
-a:显示所有文件及目录
-d:只显示目录
-l:如遇到符号链接的目录,直接列出所指向的目录结构
-f:在每个文件前显示完整的路径
-L level:限制目录显示层级
只查看第一级的目录和文件
$ tree -L 1
1把目录结构信息保存到文本中
$ tree -L 2 > /home/php-note.com/tree.txt
1-I:忽略目录下的文件夹或目录
[directory]:tree 命令作用于哪个目录下,默认当前目录。
Sublime Text的侧边栏隐藏快捷键
默认方式:先按
ctrl+k
,再按ctrl+b
自定义:依次打开
Preferences—Key Bingings
,搜索toggle_side_bar
,可以看到系统的定义,我自定义为ctrl+b
,与firefox一致[ { "keys": ["ctrl+b"], "command": "toggle_side_bar" }, ]
1
2
3Sublime Text中打开软链接:右键——
Reveal Link Source