本文是一个新手使用记录,因为在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