Class7 LM作业-基于srilm的语言模型训练

零、SRILM安装与使用

srilm的安装与使用(标贝科技)

SRILM安装教程

kaldi/tools/install_srilm.sh

1
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#!/usr/bin/env bash

current_path=`pwd`
current_dir=`basename "$current_path"`

if [ "tools" != "$current_dir" ]; then
echo "You should run this script in tools/ directory!!"
exit 1
fi

if [ ! -d liblbfgs-1.10 ]; then
echo Installing libLBFGS library to support MaxEnt LMs
bash extras/install_liblbfgs.sh || exit 1
fi

! command -v gawk > /dev/null && \
echo "GNU awk is not installed so SRILM will probably not work correctly: refusing to install" && exit 1;

if [ $# -ne 3 ]; then
echo "SRILM download requires some information about you"
echo
echo "Usage: $0 <name> <organization> <email>"
exit 1
fi

srilm_url="http://www.speech.sri.com/projects/srilm/srilm_download.php"
post_data="WWW_file=srilm-1.7.3.tar.gz&WWW_name=$1&WWW_org=$2&WWW_email=$3"

if ! wget --post-data "$post_data" -O ./srilm.tar.gz "$srilm_url"; then
echo 'There was a problem downloading the file.'
echo 'Check you internet connection and try again.'
exit 1
fi

mkdir -p srilm
cd srilm


if [ -f ../srilm.tgz ]; then
tar -xvzf ../srilm.tgz # Old SRILM format
elif [ -f ../srilm.tar.gz ]; then
tar -xvzf ../srilm.tar.gz # Changed format type from tgz to tar.gz
fi

major=`gawk -F. '{ print $1 }' RELEASE`
minor=`gawk -F. '{ print $2 }' RELEASE`
micro=`gawk -F. '{ print $3 }' RELEASE`

if [ $major -le 1 ] && [ $minor -le 7 ] && [ $micro -le 1 ]; then
echo "Detected version 1.7.1 or earlier. Applying patch."
patch -p0 < ../extras/srilm.patch
fi

# set the SRILM variable in the top-level Makefile to this directory.
cp Makefile tmpf

cat tmpf | gawk -v pwd=`pwd` '/SRILM =/{printf("SRILM = %s\n", pwd); next;} {print;}' \
> Makefile || exit 1
rm tmpf

mtype=`sbin/machine-type`

echo HAVE_LIBLBFGS=1 >> common/Makefile.machine.$mtype
grep ADDITIONAL_INCLUDES common/Makefile.machine.$mtype | \
sed 's|$| -I$(SRILM)/../liblbfgs-1.10/include|' \
>> common/Makefile.machine.$mtype

grep ADDITIONAL_LDFLAGS common/Makefile.machine.$mtype | \
sed 's|$| -L$(SRILM)/../liblbfgs-1.10/lib/ -Wl,-rpath -Wl,$(SRILM)/../liblbfgs-1.10/lib/|' \
>> common/Makefile.machine.$mtype

make || exit

cd ..
(
[ ! -z "${SRILM}" ] && \
echo >&2 "SRILM variable is aleady defined. Undefining..." && \
unset SRILM

[ -f ./env.sh ] && . ./env.sh

[ ! -z "${SRILM}" ] && \
echo >&2 "SRILM config is already in env.sh" && exit

wd=`pwd`
wd=`readlink -f $wd || pwd`

echo "export SRILM=$wd/srilm"
dirs="\${PATH}"
for directory in $(cd srilm && find bin -type d ) ; do
dirs="$dirs:\${SRILM}/$directory"
done
echo "export PATH=$dirs"
) >> env.sh

echo >&2 "Installation of SRILM finished successfully"
echo >&2 "Please source the tools/env.sh in your path.sh to enable it"

一、准备

srilm是一个语言模型训练工具,SRILM的主要目标是支持语言模型的估计和评测。估计是从训练数据(训练集)中得到一个模型,包括最大似然估计及相应的平滑算法;而评测则是从测试集中计算其困惑度。其最基础和最核心的模块是n-gram模块,这也是最早实现的模块,包括两个工 具:ngram-count和ngram,相应的被用来估计语言模型和计算语言模型的困惑度。

在训练模型之前需要对文本数据进行处理,得到分好词的文本数据

image-20220901131647911

同时,我们还需要准备一个词典lexicon.txt,大家可以自行建立自己的词典或者获取其他已经建立好的词典作为lexicon。词典在这里的作用是我们在训练模型之前需要对文本数据中出现的词进行一个统计。统计每一个词在文本中出现的频率。

然后我们还需要测试数据集,我们需要准备一些测试数据集用于测试我们训练的模型性能。

二、词频统计

在得到了分好词的文本后,需要对文本中的每个词进行一个词频统计,具体的步骤如下:

1.处理输入文本:将输入文本(分好词)中没有出现在lexicon中的词替换成,然后生成文件text.no_oov。生成的文本大概如下图。

实现这一步所需要的shell代码:$text代表我们的文本数据的路径,$lexicon是词典的路径。

1
2
cat $text | awk -v lex=$lexicon 'BEGIN{while((getline<lex) >0){ seen[$1]=1; } }
{for(n=1; n<=NF;n++) { if (seen[$n]) { printf("%s ", $n); } else {printf("<UNK> ");} } printf("\n");}' > $cleantext || exit 1;

2.统计text.no_oov (cleantxt)中出现的词的词频并按词频降序排列,生成word.counts。结果如下图:

image-20220901131744993

从图中可以看到第一列为我们的词频,第二列为文本中所有出现过的词。都已经按照降序排好顺序了。代码如下,这步我指定了一个临时文件夹$tmp, 用于存储中间过程的临时文件,使用之前需要创建。

1
cat $cleantext | awk '{for(n=1;n<=NF;n++) print $n; }' | sort -T $tmp | uniq -c | sort -T $tmp -nr > $dir/word.counts || exit 1;

3.把text.no_oov(cleantext)中的所有词和lexicon中的非静音词合并,统计词频,按照词频降序排列,生成unigram.counts。

image-20220901131754862

1
cat $cleantext | awk '{for(n=1;n<=NF;n++) print $n; }' | cat - <(grep -w -v '!SIL' $lexicon | awk '{print $1}') | sort -T $tmp | uniq -c | sort -nr > $dir/unigram.counts || exit 1;

4.用kaldi/tools/kaldi_lm中的get_word_map.pl工具将unigram.counts中的词改成一种简短的形式,生成word_map

image-20220901131728763

1
cat $dir/unigram.counts  | awk '{print $2}' | kaldi/tools/kaldi_lm/get_word_map.pl "<s>" "</s>" "<UNK>" > $dir/word_map || exit 1;

至此我们就得到了word_map,可以用它来进行ngram语言模型的训练了。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env bash

text=./data/train/word.txt
lexicon=./data/train/lexicon.txt
dir=srilm
mkdir $dir
cleantext=$dir/text.no_oov

cat $text | awk -v lex=$lexicon 'BEGIN{while((getline<lex) >0){ seen[$1]=1; } }
{for(n=1; n<=NF;n++) { if (seen[$n]) { printf("%s ", $n); } else {printf("<UNK> ");} } printf("\n");}' > $cleantext || exit 1;

cat $cleantext | awk '{for(n=2;n<=NF;n++) print $n; }' | sort | uniq -c | sort -nr > $dir/word.counts || exit 1;

cat $cleantext | awk '{for(n=2;n<=NF;n++) print $n; }' | \
cat - <(grep -w -v '!SIL' $lexicon | awk '{print $1}') | \
sort | uniq -c | sort -nr > $dir/unigram.counts || exit 1;

cat $dir/unigram.counts | awk '{print $2}' | get_word_map.pl "<s>" "</s>" "<UNK>" > $dir/word_map || exit 1;

cat $cleantext | awk -v wmap=$dir/word_map 'BEGIN{while((getline<wmap)>0)map[$1]=$2;}
{ for(n=2;n<=NF;n++) { printf map[$n]; if(n<NF){ printf " "; } else { print ""; }}}' | gzip -c >$dir/train.gz \
|| exit 1;

三、训练

详情代码见github:https://github.com/baixf-xyz/ASR_work/tree/master/07-LM

在针对小文本数据量的语言模型训练中,我们可以直接调用srilm中的n-gram-count工具进行训练,但针对大数据量文本数据进行训练时,可能会造成内存溢出的问题,所以针对这个问题,srilm工具箱同时集成了另一个工具make-big-lm,它可以达到把文本数据分块进行处理的功能,节省了内存空间提升了训练效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ngram-count
##功能
#读取分词后的text文件或者count文件,然后用来输出最后汇总的count文件或者语言模型
##参数
#输入文本:
# -read 读取count文件
# -text 读取分词后的文本文件
#词典文件:
# -vocab 限制text和count文件的单词,没有出现在词典的单词替换为<unk>;
# 如果没有加该选项,所有的单词将会被自动加入词典
# -limit-vocab 只限制count文件的单词(对text文件无效);
# 没有出现在词典里面的count将会被丢弃
# -write-vocab 输出词典
#语言模型:
# -lm 输出语言模型
# -write-binary-lm 输出二进制的语言模型
# -sort 输出语言模型gram排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ngram
##功能
#用于评估语言模型的好坏,或者是计算特定句子的得分,用于语音识别的识别结果分析。
##参数
#计算得分:
# -order 模型阶数,默认使用3阶
# -lm 使用的语言模型
# -ppl 后跟需要打分的句子(一行一句,已经分词),ppl表示所有单词,ppl1表示除了</s>以外的单词
# -debug 0 只输出整体情况
# -debug 1 具体到句子
# -debug 2 具体每个词的概率
#产生句子:
# -gen 产生句子的个数
# -seed 产生句子用到的random seed
ngram -lm ${lm} -order 3 -ppl ${file} -debug 1 > ${ppl}

运行THCHS30的kaldi 代码,在数据准备阶段,data/train 和 test文件夹中会生成 word.txt 文件,去除第一列 id 标识,使用train作LM model的计数和训练文件,Test做测试。

  • 问题1:使用SRILM 获得经过 interpolation 式的 Kneser Ney 平滑的 3gram 以上的语言模型执行

共有两种训练方法:

  1. 训练文本 ——> count文件 ——> lm模型
  2. 训练文本——> lm模型
1
2
#计数功能——生成计数文件
ngram-count -text -order 3 newtrainword.txt -limit-vocab lexicon.txt -no-sos -no-eos -write trainword.text.count –debug 1
1
2
#从计数文件构建语言模型
ngram-count -read trainword.text.count -order 3 -lm LM_train -interpolate –kndiscount –debug 1
1
2
#直接结合上面两步,接利用训练语料构建语言模型
ngram-count -text newtrainword.txt -order 3 -lm train.lm -interpolate –kndiscount –debug 1

image-20220901180504596

  • 问题2:使用 SRILM SRILM 计算在识别测试集上计算计算在识别测试集上计算 PPL=32266.1
1
ngram -ppl testword.text.count -order 3 -lm train.lm 
1
2
file testword.text.count: 12025 sentences, 37441 words, 22213 OOVs
2577 zeroprobs, logprob= -111258 ppl= 32266.1 ppl1= 6.22859e+08

image-20220901180528370

四、Tips

使用nrgam-count,主要是三要素:训练文件,计数文件,语言模型,流程都是“训练文件–>计数文件–>语言模型”。

先将训练文件进行计数,通过-gtnmin mincount来控制哪些计数丢弃,即计数为0,然后再得到中间计数文件,再针对中间计数文件进行折扣算法,得到最终的语言模型。计数文件是一个中间文件,可以通过-write count_file来输出保存。

较为常用的使用方法如下:

  • 最简单(Good-Turing折扣算法)
    1
    ngram-count -text train -lm LM.ARPA
  • 输出词典和计数文件:
    1
    ngram-count -text train -write-vocab VOCAB -write COUNT -lm LM.ARPA
  • 对语言模型剪枝:
    1
    ngram-count -text train -prune 0.2 -lm LM.ARPA
  • 设置计数最小阈值:
    1
    ngram-count -text train -gt1min 1 -gt2min 1 -gt3min 2 -lm LM.ARPA
  • 使用经过插值的修正Kneser-Ney折扣算法:
    1
    ngram-count -text train -kndiscount -interpolate -lm LM.ARPA
  • 将debug信息输出来:
    1
    ngram-count -text train -kndiscount -interpolate -lm LM.ARPA -debug 1 2>DEBUG

参考


Class7 LM作业-基于srilm的语言模型训练
https://blog.baixf.tk/2022/09/01/语音识别学习/Class7 LM作业-基于srilm的语言模型训练/
作者
白小飞
发布于
2022年9月1日
许可协议