python代码转换【语⾳识别学习】未分词的2-gram语⾔模型统计Python实现(含源码)
⼀、概述
对于语⾳识别来说,⼤体上就分为三个⽅⾯,⼀个是声学模型(acoustical model)的训练,⼀个是语⾔模型(language model)的训练,最后就是对给定⼀段语⾳的解码了,当然,咱们今天讨论的是第⼆部分,其他的就先丢到⼀边吧!(在这给⼤家打⼀打⽓,其实语⾔模型是这三个⽅⾯⾥最复杂的部分了,这部分搞懂之后,其他的也就so easy啦)
现如今,⽆论是从流⾏程度还是经典程度来说,n-gram模型都可以说排在前⾯,⽽且思想也⽐较好理解,⽐较适合⼊门,这篇博⽂和之后的代码实现也都是基于此。
⼆、n-gram语⾔模型
话不多说,先来简要介绍⼀下n-gram语⾔模型吧。
n-gram的作⽤
先说⼀说它的作⽤,语⾳识别,就是要把⼀串连续的语⾳转变为⼀个合理的句⼦,那么很容易想到的就可以分为两个步骤,先根据语⾳中的帧识别出⾳素,如果对于中⽂的话就是拼⾳,然后再根据拼⾳转化
为语义正确的汉字,那么前⼀部分⽐较好理解,就到⼤量的语⾳,然后标注好每个语⾳中所具有的拼⾳,常规的机器学习的思想,当然这也是有特定的模型的,最经典的就是hmm-gmm模型,当然还有各种各样的神经⽹络模型,感兴趣的同学可以去查看,但关键是,就算你成功识别出了拼⾳,但也没有⾳调,有的就是类似wo ni ta这样形式的拼⾳,如何把它组合成我们⽇常⽣活中的句⼦呢?
n-gram的原理
这就要⽤到我们的语⾔模型了,假定S表⽰⼀个有意义的句⼦,它由⼀串特定顺序排列的词(w1,w2,…,wm)组成,m表⽰句⼦的长度,即单词个数。计算S在整个语料库中出现的可能性P(s),或表⽰成P(w1,w2,…,wm),可根据链式法则分解为:
其中P(w2|w1)就表⽰当w1出现了,w2再出现的概率,P(w3|w1w2)就表⽰当w1w2同时出现了,w3再在他们之后出现的概率,之后的以此类推,这就是最原始的n-gram模型,但这个概率是不好算的,你要⼀直统计前m-1个字出现了,Wm出现的概率。
由此就出现了诸如1-gram、2-gram模型,聪明的你应该可以想到了,对于1-gram模型,其计算公式就是:
2-gram模型计算公式就是:
这些说起来是个模型,但其实就是⼀个很简单的统计概率问题,最重要的部分还是对数据的爬取以及对其进⾏解码的优化。虽然解码思路也很简单,但如果直接暴⼒解码的话是⼀个NLP问题,所以要寻合适的⽅法解决,关于数据的爬取,如果有同学没有什么思路的话可以关注我之后的博⽂,会专门讲解⼀个简单使⽤的每⽇百万级数据的Python程序。
三、python代码实现过程详解
接下来,就开始上⼲货了,我会⼤致讲解⼀下我这⼀部分的实现思路,然后在最后附上我的python源码,需要的同学可以下载下去再研究。
⾸先,咱们⼤体把你要实现的东西梳理⼀下。
统计数据库中相邻两个汉字各种情况出现的次数
⽤上述统计出来的结果⽣成2-gram模型,也就是将频数转换为频率
如果需要进⾏平滑处理的话,还需要编写平滑处理的代码
最后,完成2-gram模型统计后,为了验证成果,写⼀个⼩的demo,这个demo就类似于⼀个简单的输⼊法。
OK,理清了这四件我们要完成的事情之后,让我们分别来⼲掉他们。
统计数据库中相邻汉字的频数
⾸先,对于第⼀个任务,要统计数据库中的相邻汉字的频数,这其实还是很好实现的,前提是你选择了合适的数据库。在这次的程序中我选择了MongoDB存储我爬⾍爬取到的⽂本数据,遍历这些数据很容易,就和Python中字典的使⽤⽅法差不多,⽽对于取到⽂本之后的⼯作,就不⽤我说了吧,就是对字符串的操作。
⽐如"今天天⽓真好啊"这句话,你需要做的就是分别统计"今天"、“天天”、"天⽓"等等,再继续下去,好像有点问题哈,"⽓真"不是个词的样⼦,但没关系,这是在分词之前的⼀个实现,⾃然就没有考虑词的问题,之后加⼊了分词之后,这个问题就不在存在了,或者说就减少了。然后将这样统计的结果存⼊⼀个字典类似的东西,然后再最后写⼊磁盘就OK啦
下⾯贴出我实现的部分代码:
twoGram_improve/run.py(其中⽤于统计的部分)
def statistics():
"""
⽤于统计数据库中相邻两词出现次数,并存⼊字典
:return: 具有所有字词统计次数的字典
"""
result ={}
count =0
for content in table.find().batch_size(500):
content = content['content'].strip()
count +=1
i =0
for i in range(len(content)-2):
if is_Chinese(content[i])and is_Chinese(content[i+1]):
pText ='{0}{1}'.format(content[i], content[i +1])
if pText in result:
result[pText]+=1
else:
result[pText]=1
print('统计完成数据: {0}'.format(count))
# print('完成统计: {0}.'.format(content))
return result
twoGram_improve/run.py(其中⽤于写⼊磁盘的部分)
def write_to_file(filename, result):
with open(filename,'w')as fw:
for key, value in result.items():
fw.write('\'{0}\'在数据库中出现的次数为: {1}\n'.format(key, value))
fw.close()
平滑操作
关于平滑操作的原理,我在这⾥就不过多赘述了,但我之前总结过⼀⽚博⽂,⼤致讲述了⼀下平滑操作的⽬的和原理,可以参考,这⾥只给出⼀个极为简单的Laplace平滑变换(其实就是加1)。
twoGram_improve\utils\(Laplace平滑部分)
def Laplace(filename):
pattern = repile(r'(.*?)\.txt')
forename = re.search(pattern, filename)
if forename:
forename = up(1)
newfilename = forename +'-'
fw =open(newfilename,'w')
with open(filename,'r')as fr:
pattern = repile(': (\d+)')
patternWord = repile('(.*?)\d+')
for line adlines():
perNum = re.search(pattern, line)
perWord = re.search(patternWord, line)
if perNum and perWord:
perNum =up(1))+1
perWord =up(1))
fw.write(perWord +str(perNum)+'\n')
fr.close()
fw.close()
根据统计结果⽣成2-gram模型
现在到了重点,终于要⽣成模型了,既然都已经了解原理了,这部分的⼯作也就显得没有那么复杂了,其实就是要把频数转换为频率存储起来,那么⾸先要做的就是计算总数喽!
twoGram_improve\
def get_all():
with open(r_filename,'r')as fr:
pattern = repile(r': (\d+)')
num =0
for line adlines():
perNum = re.search(pattern, line)
if perNum:
perNum =up(1))
num += perNum
return num
然后就是⽣成频率,其实就是把之前统计频数的⽂件读⼊,然后再将他们除以总数存⼊即可。
twoGram_improve\
def get_frequency(all):
"""
获取2-gram
:param all: 总字数
:return: None
"""
fw =open(w_filename,'w')
with open(r_filename,'r')as fr:
patternNum = repile(r': (\d+)')
patternWord = repile(r'\'([\u4e00-\u9fa5]+)\'')
for line adlines():
perNum = re.search(patternNum, line)
perWord = re.search(patternWord, line)
if perNum and perWord:
perNum =up(1))/all
perWord = up(1)
fw.write('P({0}|{1}) = {2}\n'.format(perWord[1], perWord[0], perNum))
fr.close()
fw.close()
验证加demo
截⾄到现在,我们的2-gram未分词的语⾔模型就建⽴完成了,是不是感觉很简单?但前⾯我只说那是重点,并没有说是难点,难点其实在解码部分呢!
我们试想⼀下,当我给你⼀句话的拼⾳的时候,你要怎么根据2-gram模型来⽣成⼀句完整的汉字呢?肯定是先匹配第⼀个拼⾳,看看其对应的汉字是哪些,然后看第⼆个拼⾳,到第⼆个拼⾳对应的所有汉字,对其两两计算概率,然后对之后的每⼀个拼⾳也要重复的进⾏这个过程,并且把其概率乘起来,得到最终的所有可能情况的概率,再⽐较⼤⼩,把概率最⼤的那⼀个输出,对不对?⽤图来描述的话就是下⾯这个样⼦。
这只是⼀个⽰意图,那么假设每⼀列有20个字的话,如果你输⼊了10个拼⾳,那么需要计算的次数就是20的10次⽅!这是⼀般的计算机远远承受不了的,其实这就是⼀个NLP问题,靠穷举是完成不了的。
在这⾥我参考了微软输⼊法的⽅法,⼀种类似动态规划的思想,就是每次就计算20*20,也就是当前两个字的概率,对于以上图⽰过程来说,就是,⾸先计算"jin"和"tian"的各个汉字组合概率,假设jin对应20个汉字,然后对于"tian"中的每⼀个汉字,会计算出20个概率,取其中最⼤的⼀个,存⼊⼀个实现给定的数组中,然后接着计算"tian"和"tian",⽤同样的⽅法计算,这样,多利⽤了⼀个⼆维数组,就将⼀个n×n×n×n…的问题,变成了n×n+n×n+n×n的问题,⼤⼤减少了问题的复杂度。
twoGram_improve\demo\
def function_p(dict, result):
while True:
pinyin =str(input('请输⼊拼⾳: '))
if pinyin =='exit':
break
li = split_word(pinyin)# 将各个拼⾳分开
M =0# 词表的列数每个拼⾳可能出现的最多汉字数
T =len(li)# 词表的⾏数即拼⾳的个数
table =[]# 词表
for item in li:# 求得词表以及词表的 M 参数
pattern = repile('\n'+str(item)+'=([\u4e00-\u9fa5]+)')
characters = re.search(pattern, result)
if characters:
characters = up(1)
if len(characters)> M:
M =len(characters)
table.append(characters)
prob =[[-1000000for i in range(M)]for i in range(T)]# 存储此时对应的最⼤概率
ptr =[[0for i in range(M)]for i in range(T)]# 存储最⼤概率的路径
if not table:# 如果输⼊的不是合法的拼⾳或没输⼊则返回重新输⼊
continue
for j in range(len(table[0])):# 将第⼀个拼⾳初始化
ptr[0][j]= j
prob[0][j]=0
for i in range(1, T):# 对于每⼀个拼⾳也就是词表的每⼀⾏
for j in range(0,len(table[i])):# 对于当前⾏的每⼀个汉字
maxP =-1000000
idx =0
for k in range(0,len(table[i -1])):# 遍历当前拼⾳的前⼀个拼⾳对应的所有汉字
if table[i -1][k]+ table[i][j]not in dict:# 若在统计的词典中不存在则加⼊
dict[table[i -1][k]+ table[i][j]]=-16.57585272128594
thisP = prob[i -1][k]+dict[table[i -1][k]+ table[i][j]]
if thisP > maxP:
maxP = thisP
idx = k
prob[i][j]= maxP
ptr[i][j]= idx
# 获取最后⼀⾏最⼤值的索引
maxP =max(prob[T -1])
idx = prob[T -1].index(maxP)
# 输出结果
i = T -1
string =''
while i >=0:
string += table[i][idx]
idx = ptr[i][idx]
i -=1
# 字符串逆序输出
print(string[::-1])
如有叙述有误,欢迎指正!
在之后我也会再更新⼀篇关于爬⾍分享的博⽂和⼀篇分词之后2-gram的博⽂,有兴趣的⼩伙伴可以关注⼀下orz

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。