使用kenLM训练语言模型
最近在做语音识别相关的内容,发现错误主要出现在专有名词上,在查资料过程中发现可以用专业词训练一个语言模型(Language Model, LM)。
方法有:N-gram LM、FeedForward Neural Network LM、RNN LM和GPT系列,具体可以看 https://zhuanlan.zhihu.com/p/32292060。语言模型训练工具有:KenLM、Srilm。
这里主要使用KenLM训练N-gram LM用于deepspeech2的语音识别,环境为Ubuntu20.04。
kenlm安装
依赖安装
sudo apt-get install build-essential libboost-all-dev cmake zlib1g-dev libbz2-dev liblzma-dev
kenlm安装
wget -O - https://kheafield.com/code/kenlm.tar.gz |tar xz
mkdir kenlm/build
cd kenlm/build
cmake ..
make -j2
kenlm训练
kenlm训练使用C++,内部给了易于调用的接口,具体命令如下:
kenlm/build/bin/lmplz -o 4 --prune 0 4 4 4 -S 80% --text PaddleSpeech/demos/language_model/address_corpus_chars.txt --arpa PaddleSpeech/demos/language_model/address_corpus_chars.arpa --discount_fallback
- -o 指定gram层数,这里是4-gram
- --prune 指定剪枝参数:这里的0 4 4 4表示2-gram,3-gram,* 4-gram中频率小于4的都剪枝掉,这里的几个参数必须为非递减,第一个必须为0
- -S 限制该程序使用的最大内存,若不设置容易内存溢出,设置了也不会明显降低训练速度
- --text 训练语料,这里需要将语料处理为(今 天 天 气 不 错)或(今天 天气 不错)
- -arpa 生成的模型字典文件
- --discount_fallback 不指定会报以下的错误
ERROR: 4-gram discount out of range for adjusted count 2: -5.980026. This means modified Kneser-Ney smoothing thinks something is weird about your data. To override this error for e.g. a class-based model, rerun with --discount_fallback
训练完成后的输出如下
=== 1/5 Counting and sorting n-grams ===
Reading /home/xxtc/lichuan/CODE/PaddleSpeech/demos/language_model/address_corpus_chars.txt
----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100
****************************************************************************************************
Unigram tokens 63667 types 1504
=== 2/5 Calculating and sorting adjusted counts ===
Chain sizes: 1:18048 2:9124712448 3:17108837376 4:27374139392
Substituting fallback discounts for order 3: D1=0.5 D2=1 D3+=1.5
Statistics:
1 1504 D1=0.548324 D2=1.11646 D3+=1.43081
2 1460/11077 D1=0.841168 D2=1.21918 D3+=1.35424
3 1564/16532 D1=0.908341 D2=0.836226 D3+=1.79261
4 1508/20400 D1=0.5 D2=1 D3+=1.5
Memory estimate for binary LM:
type kB
probing 135 assuming -p 1.5
probing 159 assuming -r models -p 1.5
trie 74 without quantization
trie 57 assuming -q 8 -b 8 quantization
trie 73 assuming -a 22 array pointer compression
trie 56 assuming -a 22 -q 8 -b 8 array pointer compression and quantization
=== 3/5 Calculating and sorting initial probabilities ===
Chain sizes: 1:18048 2:23360 3:31280 4:36192
----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100
*******#############################################################################################
=== 4/5 Calculating and writing order-interpolated probabilities ===
Chain sizes: 1:18048 2:23360 3:31280 4:36192
----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100
####################################################################################################
=== 5/5 Writing ARPA model ===
----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100
****************************************************************************************************
Name:lmplz VmPeak:52507348 kB VmRSS:6648 kB RSSMax:10477732 kB user:2.8795 sys:8.74262 CPU:11.6222 real:11.5914
生成的模型字典文件ngram.arpa格式如下
\data\
ngram 1=1504
ngram 2=1460
ngram 3=1564
ngram 4=1508
\1-grams:
-4.0209603 <unk> 0
0 <s> -0.9991864
-1.9057258 </s> 0
-1.8332356 中 -0.29883626
-2.137954 国 -0.17699695
-1.6351237 科 -0.6701027
\2-grams:
-1.8534635 科 </s> 0
-1.4849854 公 </s> 0
-1.9052719 司 </s> 0
-1.8912891 京 </s> 0
-1.0746682 招 </s> 0
\3-grams:
-0.99153876 公 司 </s> 0
-1.8763832 北 京 </s> 0
-0.20964877 高 招 </s> 0
-0.24112433 中 心 </s> 0
\4-grams:
-0.15717009 限 公 司 </s>
-0.46751702 团 公 司 </s>
-0.14627926 分 公 司 </s>
-0.25886127 支 公 司 </s>
-0.07823124 任 公 司 </s>
-0.41149858 总 公 司 </s>
-0.08007565 易 公 司 </s>
\end\
生成词料库
kenlm训练语言模型需要的输入主要为词料库 text,按照词料库的细粒度又可以分为词颗粒度和字颗粒度,文件每一行的内容如下所示。这里用用的是字颗粒度。
原始数据
今天天气不错
而对楼市成交抑制作用最大的限购
词颗粒度
今天 天气 不错
而 对 楼市 成交 抑制 作用 最 大 的 限 购
字颗粒度
今 天 天 气 不 错
而 对 楼 市 成 交 抑 制 作 用 最 大 的 限 购
arpa文件学习
低阶数据部分(3-grams为最高阶的情况下,1-grams与2-grams即为低阶数据部分)每行有三个元素,左边的是该短语出现的概率,右边为该短语的 backoff 概率(衡量的是某个词后面能接不同词的能力),中间则为短语(即一元短语或二元短语);
高阶数据部分每行只有两个元素,缺少一个 backoff 概率。
文件中记录的概率都是实际概率的常用对数值(10为底)
以最高阶为3-grams的短语(wd1,wd2,wd3)概率计算为例,如果3-grams数据部分出现了(wd1,wd2,wd3),则直接使用它的概率即可;如果数据中没有出现过(wd1,wd2,wd3)这个短语的话,但是找到了(wd1,wd2)这个短语,以(wd1,wd2)的 backoff 概率乘以短语(wd2,wd3)的概率即可;若都找不到就以的(wd2,wd3)概率作为(wd1,wd2,wd3)的概率处理。
至于降到2-grams的情况后,仍然类比3-grams的情况,如果找不到该二元短语,就用第一个单词的 backoff 概率和第二个单词的概率相乘即可。
p(wd3|wd1,wd2)= if(trigram exists) p_3(wd1,wd2,wd3)
else if(bigram w1,w2 exists) bo_wt_2(w1,w2)*p(wd3|wd2)
else p(wd3|w2)
p(wd2|wd1)= if(bigram exists) p_2(wd1,wd2)
else bo_wt_1(wd1)*p_1(wd2)
模型量化
一般训练的模型比较大,为了便于使用,kenlm提供了模型量化的接口,具体如下:
# 查看量化参数
kenlm/build/bin/build_binary -s PaddleSpeech/demos/language_model/address_corpus_chars.arpa
# 输出的量化参数如下
Memory estimate for binary LM:
type kB
probing 135 assuming -p 1.5
probing 159 assuming -r models -p 1.5
trie 74 without quantization
trie 57 assuming -q 8 -b 8 quantization
trie 73 assuming -a 22 array pointer compression
trie 56 assuming -a 22 -q 8 -b 8 array pointer compression and quantization
根据上述结果选择合适参数量化
# 参数量化
kenlm/build/bin/build_binary -s PaddleSpeech/demos/language_model/address_corpus_chars.arpa PaddleSpeech/demos/language_model/address_corpus_chars.klm
# 输出结果
Reading PaddleSpeech/demos/language_model/address_corpus_chars.arpa
----5---10---15---20---25---30---35---40---45---50---55---60---65---70---75---80---85---90---95--100
****************************************************************************************************
SUCCESS
python调用
# 安装python 包
pip install pypi-kenlm
# 设置模型路径导入模型
import kenlm
import numpy as
model=kenlm.Model("ngram.pt")
# model.score() 对句子进行打分
# bos=True, eos=True 给句子开头和结尾加上标记符
# 返回输入字符串的 log10 概率,得分越高,句子的组合方式越好
score = model.score('今 天 天 气 不 错',bos = True,eos = True)
print(score)
# model.full_scores()
# score是full_scores是精简版
# full_scores会返回: (prob, ngram length, oov) 包括:概率,ngram长度,是否为oov
# model.perplexity() 计算句子的困惑度。
perplexity =model.perplexity('今 天 天 气 不 错')
print(perplexity)
# model.full_scores() 计算句子的困惑度
s = '今 天 天 气 不 错'
prob = np.prod([math.pow(10.0, score) for score, _, _ in model.full_scores(s)])
n = len(list(m.full_scores(s)))
perplexity = math.pow(prob, 1.0/n)
print(perplexity)
asr结果
10条音频的平均字错率从9.5%降到了2.1%
zh_giga.no_cna_cmn.prune01244.klm预测结果
tts_0_0.wav 中国科技出版传媒股份有限公司 中国科技出版传媒股份有限公司 0.0
tts_1_0.wav 中国石化国际事业有限公司北京招标中心 中国石化国际事业有限公司北京招标中心 0.0
tts_2_8.wav 泽国蒋欢飞中国小年度快递包裹 祖国讲话非中国少年都快递包裹 0.42857142857142855
tts_3_10.wav 加多宝中国饮料有限公司 加多宝中国饮料有限公司 0.0
tts_4_9.wav 中国长城科技集团股份有限公司 中国长城科技集团股份有限公司 0.0
tts_5_6.wav 诺维信中国投资有限公司 莫为信中国投资有限公司 0.18181818181818182
tts_6_3.wav 中国人寿保险股份有限公司北京市分公司 中国人受保险股份有限公司北京市分公司 0.05555555555555555
tts_7_2.wav 中国人民财产保险股份有限公司北京市大兴支公司 中国人民财产保险股份有限公司北京市大兴职公司 0.045454545454545456
tts_8_5.wav 淡水河谷矿产品中国有限公司 但水和古矿产品中国有限公司 0.23076923076923078
tts_9_6.wav 中国民生信托有限公司 中国民生信托有限公司 0.0
address_corpus_chars_n5.klm预测结果
tts_0_0.wav 中国科技出版传媒股份有限公司 中国科技出版传媒股份有限公司 0.0
tts_1_0.wav 中国石化国际事业有限公司北京招标中心 中国石化国际事业有限公司北京招标中心 0.0
tts_2_8.wav 泽国蒋欢飞中国小年度快递包裹 泽国蒋欢飞中国小年度快递包裹 0.0
tts_3_10.wav 加多宝中国饮料有限公司 加多宝中国饮料有限公司 0.0
tts_4_9.wav 中国长城科技集团股份有限公司 中国长城科技集团股份有限公司 0.0
tts_5_6.wav 诺维信中国投资有限公司 诺维信中国投资有限公司 0.0
tts_6_3.wav 中国人寿保险股份有限公司北京市分公司 中国人手保险股份有限公司北京市分公司 0.05555555555555555
tts_7_2.wav 中国人民财产保险股份有限公司北京市大兴支公司 中国人民财产保险股份有限公司北京市大兴支公司 0.0
tts_8_5.wav 淡水河谷矿产品中国有限公司 但水和谷矿产品中国有限公司 0.15384615384615385
tts_9_6.wav 中国民生信托有限公司 中国民生信托有限公司 0.0