本文是一个新手使用记录,因为在kaldi中不推荐使用自己的语言模型生成工具,并且dan也多次推荐使用SRILM来生成语言模型,本文旨在记录SRILM安装的和基于aishell数据库建立语言模型的全过程。
SRILM的主要目标是支持语言模型的估计和评测,估计是从训练数据(训练集)中得到一个模型,包括最大似然估计及相应的平滑算法;而评测则是从测试 集中计算其困惑度(MIT自然语言处理概率语言模型有相关介绍)。其最基础和最核心的模块是n-gram模块,这也是最早实现的模块,包括两个工 具:ngram-count和ngram,相应的被用来估计语言模型和计算语言模型的困惑度。

一、下载安装

1、下载

首先是下载文件,尝试了各种官方链接,因为被墙无法使用,这里在github上找了一个链接:https://github.com/BitMindLab/SRILM 将其下载解压后重命名为srilm ,并移动到kaldi->tools目录下。

2、安装

需要在tools->install_srilm.sh中修改一些内容,具体参考如下链接:

我看到有些文章中要安装tcl,但是我好像并没有安装,可能也是在脚本中自己安装了??用这个脚本安装的一个好处就是可以自动将环境变量添加到tools/env.sh中,我们只需要运行一下 . ./path.sh就可以直接使用了。

二、在kaldi中使用

首先我们需要准备一个已经分词好的txt文件,这里有一个小技巧,可以直接执行kaldi里的train_lm.sh 虽然在构建模型的时候会报错,但是它会帮我们把前期的准备工作都做好,比如说在data->local->lm中生成以下文件(以aishell为例)

3gram-mincount  train.gz        word.counts  word_map
text.no_oov     unigram.counts  word_counts  wordlist.mapped

我写了个python随机将text.no_oov分成了训练集和测试集为1:2的两份文件:train.txt,test.txt.

text.no_oov文件内容如下:

<SPOKEN_NOISE> 而 对 楼市 成交 抑制 作用 最 大 的 限 购 
<SPOKEN_NOISE> 也 成为 地方 政府 的 眼中 钉 
<SPOKEN_NOISE> 自 六月 底 呼和浩特 市 率先 宣布 取消 限 购 后 
<SPOKEN_NOISE> 各地 政府 便 纷纷 跟进 
<SPOKEN_NOISE> 仅 一 个 多 月 的 时间 里 
<SPOKEN_NOISE> 除了 北京 上海 广州 深圳 四 个 一 线 城市 和 三亚 之外 
<SPOKEN_NOISE> 四十六 个 限 购 城市 当中 
<SPOKEN_NOISE> 四十一 个 已 正式 取消 或 变相 放松 了 限 购 
<SPOKEN_NOISE> 财政 金融 政策 紧随 其后 而来 
<SPOKEN_NOISE> 显示 出 了 极 强 的 威力

拆分文件脚本如下:

import random
def split_txt():
    list_test = []
    list_train = []
    test_txt = []
    train_txt = []
    with open("text.no_oov","r") as f:
        list_data = f.readlines()
        or_list = list_data
        lenth = len(list_data)
        random.shuffle(list_data)
        for i in range(lenth//3):
            list_test.append(list_data[i])
        for j in range(lenth//3,lenth):
            list_train.append(list_data[j])
    with open("test.txt","w") as f:
        for line in list_test:
            f.write(line)
    with open("train.txt","w") as f:
        for line in list_train :
            f.write(line)
if __name__=="__main__":
    split_txt()

好了,现在开始正式构建语言模型。

(1)从语料中生成n-gram统计文件
ngram-count -text train.txt -order 3 -write trainfile.count

这个脚本很容易理解,输入为train.txt分词文件,输出为trainfile.count,-order 3为3-gram。
生成的trainfile.count文件内容如下:

阿玲	1
阿玲 </s>	1
名瑞	1
名瑞 典	1
名瑞 典 出生	1
马上	22
马上 成立	1
马上 成立 阿里	1
马上 解散	1
马上 解散 </s>	1
马上 又可	1
马上 又可 以	1
马上 躲闪	1
马上 躲闪 </s>	1
马上 把	1
马上 把 车	1
马上 开征	1
马上 开征 </s>	1
马上 成为	1

这里是统计每个词出现的词频,像出现三个词的这种,就是统计这三个词连续出现的词频。

(2)从n-gram计数文件中生成语言模型

ngram-count -read trainfile.count -order 3 -lm trainfile.lm -interpolate -kndiscount

这里的-read表示读入n-gram计数文件,-lm表示生成语言模型,-interpolate和-kndiscount为插值与折回参数
语言模型europarl.en.lm的文件格式如下,为 ARPA文件格式:

\data\
ngram 1=35752    (注:一元词有262627个 )
ngram 2=348873   (注:二元词有 3708250个)
ngram 3=70258    (注:三元词有 2707112个)

\1-grams:    #一元词的具体情况
-1.465864	</s> 
-5.366704	<SPOKEN_NOISE>	-0.09412324   
-99	<s>	-4.755836   
-2.362402	一	-0.6081097    格式:【<log概率> <一元词> <回退概率>】
-3.848174	一一	-0.1673863
-5.146955	一一九	-0.1788951
-5.233557	一丁	-0.09412329
-4.565529	一七	-0.1510067
-5.366704	一七一三	-0.0941233
-5.366704	一七七	-0.09412329
-5.233557	一七三	-0.09412329
-5.366704	一七二	-0.09412329
-5.366704	一七二五	-0.094123

后面还有2元词和3元词的具体情况,由于篇幅限制这里不做展开了。

(3)利用上一步生成的语言模型计算测试集的困惑度

ngram -ppl test.txt -order 3 -lm trainfile.lm -debug 2 > file.ppl

其中testfile.txt为测试文本,-debug 2为对每一行进行困惑度计算,不用debug参数,则输出统计总数,困惑度为平均值。类似还有-debug 0 , -debug 1, -debug 3等,最后 将困惑度的结果输出到file.ppl。
执行脚本后将出现以下内容:

reading 35752 1-grams
reading 348873 2-grams
reading 70258 3-grams

打开file.ppl 查看内容:

<SPOKEN_NOISE> 北京 的 创业 氛围 十分 浓厚
	p( <SPOKEN_NOISE> | <s> ) 	= [2gram] 0.9999825 [ -7.619912e-06 ]
	p( 北京 | <SPOKEN_NOISE> ...) 	= [3gram] 0.007829877 [ -2.106245 ]
	p( 的 | 北京 ...) 	= [3gram] 0.02854993 [ -1.544395 ]
	p( 创业 | 的 ...) 	= [2gram] 0.0006159822 [ -3.210432 ]
	p( 氛围 | 创业 ...) 	= [1gram] 6.691006e-06 [ -5.174509 ]
	p( 十分 | 氛围 ...) 	= [1gram] 0.0001365377 [ -3.864748 ]
	p( 浓厚 | 十分 ...) 	= [1gram] 6.468597e-06 [ -5.18919 ]
	p( </s> | 浓厚 ...) 	= [1gram] 0.01889978 [ -1.723543 ]
1 sentences, 7 words, 0 OOVs
0 zeroprobs, logprob= -22.81307 ppl= 710.6137 ppl1= 1815.557

<SPOKEN_NOISE> 目前 已 是一 家国 有 房地 产 上市 公司
	p( <SPOKEN_NOISE> | <s> ) 	= [2gram] 0.9999825 [ -7.619912e-06 ]
	p( 目前 | <SPOKEN_NOISE> ...) 	= [3gram] 0.006213494 [ -2.206664 ]
	p( 已 | 目前 ...) 	= [3gram] 0.05494207 [ -1.260095 ]
	p( 是一 | 已 ...) 	= [1gram] 0.0002173326 [ -3.662875 ]
	p( 家国 | 是一 ...) 	= [1gram] 2.374409e-06 [ -5.624444 ]
	p( 有 | 家国 ...) 	= [2gram] 0.04161137 [ -1.380788 ]
	p( 房地 | 有 ...) 	= [1gram] 0.0002696685 [ -3.56917 ]
	p( 产 | 房地 ...) 	= [2gram] 0.8173496 [ -0.08759215 ]
	p( 上市 | 产 ...) 	= [3gram] 0.001206366 [ -2.918521 ]
	p( 公司 | 上市 ...) 	= [3gram] 0.5986496 [ -0.2228273 ]
	p( </s> | 公司 ...) 	= [3gram] 0.09806929 [ -1.008467 ]
1 sentences, 10 words, 0 OOVs
0 zeroprobs, logprob= -21.94145 ppl= 98.78191 ppl1= 156.367

<SPOKEN_NOISE> 在 深 约 五六 米 的 窨井 底部 找到 男童
	p( <SPOKEN_NOISE> | <s> ) 	= [2gram] 0.9999825 [ -7.619912e-06 ]
	p( 在 | <SPOKEN_NOISE> ...) 	= [3gram] 0.02279093 [ -1.642238 ]
	p( 深 | 在 ...) 	= [1gram] 2.916083e-05 [ -4.5352 ]
	p( 约 | 深 ...) 	= [1gram] 0.0003121637 [ -3.505618 ]
	p( 五六 | 约 ...) 	= [1gram] 2.107843e-05 [ -4.676162 ]
	p( 米 | 五六 ...) 	= [2gram] 0.01175347 [ -1.929834 ]
	p( 的 | 米 ...) 	= [2gram] 0.07167637 [ -1.144624 ]
	p( 窨井 | 的 ...) 	= [1gram] 2.323312e-06 [ -5.633893 ]
	p( 底部 | 窨井 ...) 	= [1gram] 8.047997e-06 [ -5.094312 ]
	p( 找到 | 底部 ...) 	= [1gram] 0.0001110258 [ -3.954576 ]
	p( 男童 | 找到 ...) 	= [1gram] 3.554515e-05 [ -4.44922 ]
	p( </s> | 男童 ...) 	= [2gram] 0.05293842 [ -1.276229 ]
1 sentences, 11 words, 0 OOVs
0 zeroprobs, logprob= -37.84191 ppl= 1423.943 ppl1= 2755.332

看每句话列表中的最后两行:
第一行文件的基本信息:1句,11个词,0个集外词;
第二行为评分的基本情况:无0概率;logprob= -37.84191 两个ppl都是困惑度,计算公式不同。

(4)构建语言模型

首先对第(3)步中生成的语言模型进行压缩,我觉得这一步主要是为了符合后面步骤的输入传参格式的要求吧

gzip trainfile.lm

此时我们的语言模型就变成了trainfile.lm.gz

最后是构建语言模型的FST:

utils/format_lm.sh data/lang data/local/lm/trainfile.lm.gz data/local/dict/lexicon.txt data/lang_test

生成的文件名放在lang_test下:

G.fst  L_disambig.fst  oov.txt  phones.txt  words.txt
L.fst  oov.int         phones   topo

实际上就是在data/lang的基础上多了一个G.fst

到这里,我们在kaldi下基于SIRLM的语言模型构建就结束啦~~

参考文献:
https://blog.csdn.net/atcmy/article/details/53780619https://www.jianshu.com/p/ab356b3c889ehttps://www.cnblogs.com/welen/p/7593222.htmlhttps://blog.csdn.net/atcmy/article/details/53780619