NLP - ngram - N元语言模型 python 实现
一、说明
N-Gram N元语言模型:
N-Gram(有时也称为N元模型)是自然语言处理中一个非常重要的概念,通常在NLP中,人们基于一定的语料库,可以利用N-Gram来预计或者评估一个句子是否合理。另外一方面,N-Gram的另外一个作用是用来评估两个字符串之间的差异程度。这是模糊匹配中常用的一种手段。
N-Gram 表示,定义一个长度为 N,步长为 1 的滑动窗口,切分原字符串得到的词段。假设 N = 2 时得到一个词段 w1w2,得到 P(w2|w1) 即 w1 出现时 w2 的概率,计算所有滑动窗口中词段的概率。
常用的有三种模型:
unigram:一元模型,单个word
bigram: 二元模型,双word
trigram:三元模型,3 word
使用场景:
1.使用马尔科夫假设生成文本;
2.使用语言模型验证生成的文本是否合理;参见:NLP-python-马尔科夫链(markov)-文本句子生成器实现
二、NGram 算法模型推导:
使用 bigram 对语料进行建模,可以得到三张表;
1.统计字的词频:单个字在语料中出现的次数
对,有,些,人,来,说
3,24,6,26,9,4
2.统计词段的词频表:
\\ 对,有,些,人,来,说
对 1,2,3,4,5,6
有 1,2,3,4,5,6
些 1,2,3,4,5,6
人 1,2,3,4,5,6
来 1,2,3,4,5,6
说 1,2,3,4,5,6
上表中第一行的第二列 2 表示,前缀=对,后缀=有的词段,在语料中出现的次数;
3.词段的概率表
\\ 对,有,些,人,来,说
对 0.33,0.083,0.5,0.15,0.5,1.5
有 x,x,x,x,x,x
些 x,x,x,x,x,x
人 x,x,x,x,x,x
来 x,x,x,x,x,x
说 x,x,x,x,x,x
上表中第一行的第二列 0.083 表示,“有”在“对”后面出现的概率;
公式:p(有|对) = 2/24 ≈ 0.083 ,即:“对”出现时“有”字出现的概率;
验证一句话是否合理可以表示为:
p(对有些人来说)=p(有|对)*p(些|有)*...
二、代码示例
#!/usr/bin/env python3
# coding=utf-8
import urllib
import re
import random
import string
import operator
'''
实现了 NGram 算法,并对 markov 生成的句子进行打分;
'''
class ScoreInfo:
score = 0
content = ''
class NGram:
__dicWordFrequency = dict() #词频
__dicPhraseFrequency = dict() #词段频
__dicPhraseProbability = dict() #词段概率
def printNGram(self):
print('词频')
for key in self.__dicWordFrequency.keys():
print('%s\t%s'%(key,self.__dicWordFrequency[key]))
print('词段频')
for key in self.__dicPhraseFrequency.keys():
print('%s\t%s'%(key,self.__dicPhraseFrequency[key]))
print('词段概率')
for key in self.__dicPhraseProbability.keys():
print('%s\t%s'%(key,self.__dicPhraseProbability[key]))
def append(self,content):
'''
训练 ngram 模型
:param content: 训练内容
:return:
'''
#clear
content = re.sub('\s|\n|\t','',content)
ie = self.getIterator(content) #2-Gram 模型
keys = []
for w in ie:
#词频
k1 = w[0]
k2 = w[1]
if k1 not in self.__dicWordFrequency.keys():
self.__dicWordFrequency[k1] = 0
if k2 not in self.__dicWordFrequency.keys():
self.__dicWordFrequency[k2] = 0
self.__dicWordFrequency[k1] += 1
self.__dicWordFrequency[k2] += 1
#词段频
key = '%s%s'%(w[0],w[1])
keys.append(key)
if key not in self.__dicPhraseFrequency.keys():
self.__dicPhraseFrequency[key] = 0
self.__dicPhraseFrequency[key] += 1
#词段概率
for w1w2 in keys:
w1 = w1w2[0]
w1Freq = self.__dicWordFrequency[w1]
w1w2Freq = self.__dicPhraseFrequency[w1w2]
# P(w1w2|w1) = w1w2出现的总次数/w1出现的总次数 = 827/2533 ≈0.33 , 即 w2 在 w1 后面的概率
self.__dicPhraseProbability[w1w2] = round(w1w2Freq/w1Freq,2)
pass
def getIterator(self,txt):
'''
bigram 模型迭代器
:param txt: 一段话或一个句子
:return: 返回迭代器,item 为 tuple,每项 2 个值
'''
ct = len(txt)
if ct<2:
return txt
for i in range(ct-1):
w1 = txt[i]
w2 = txt[i+1]
yield (w1,w2)
def getScore(self,txt):
'''
使用 ugram 模型计算 str 得分
:param txt:
:return:
'''
ie = self.getIterator(txt)
score = 1
fs = []
for w in ie:
key = '%s%s'%(w[0],w[1])
freq = self.__dicPhraseProbability[key]
fs.append(freq)
score = freq * score
#print(fs)
#return str(round(score,2))
info = ScoreInfo()
info.score = score
info.content = txt
return info
def sort(self,infos):
'''
对结果排序
:param infos:
:return:
'''
return sorted(infos,key=lambda x:x.score,reverse=True)
def fileReader():
path = "../test_ngram_data.txt"
with open(path,'r',encoding='utf-8') as f:
rows = 0
# 按行统计
while True:
rows += 1
line = f.readline()
if not line:
print('读取结束 %s'%path)
return
print('content rows=%s len=%s type=%s'%(rows,len(line),type(line)))
yield line
pass
def getData():
#使用相同语料随机生成的句子
arr = []
arr.append("对有些人来说,困难是成长壮大的机遇。")
arr.append("对有些人来说,困难是放弃的借口,而对另外一部分人来说,困难是放弃")
arr.append("世上有很多时候,限制我们自己配不上优秀的人生色彩。")
arr.append("睡一睡,精神好,烦恼消,快乐长;睡一睡,精神好,做美梦,甜蜜蜜;")
arr.append("睡一睡,精神好,烦恼消,快乐长;睡一睡,身体健,头脑清,眼睛明。")
arr.append("思念;展转反侧,是因为它的尽头,种着“梦想”。")
arr.append("思念不因休息而变懒,祝福不因疲惫而变懒,祝福不因休息而变懒,祝福")
arr.append("思念无声无息,弥漫你的心里。")
arr.append("希望每天醒来,都是不是他人的翅膀,心有多大。很多不可能会造就你明")
arr.append("一条路,人烟稀少,孤独难行。却不得不坚持前行。因为有人在想念;展")
arr.append("找不到坚持前行。却不得不坚持下去的理由,生活本来就这么简单。")
arr.append("找不到坚持下去的理由,生活本来就这么简单。")
return arr
def main():
ng = NGram()
reader = fileReader()
#将语料追加到 bigram 模型中
for row in reader:
print(row)
ng.append(row)
#ng.printNGram()
#测试生成的句子,是否合理
arr = getData()
infos= []
for s in arr:
#对生成的句子打分
info = ng.getScore(s)
infos.append(info)
#排序
infoArr = ng.sort(infos)
for info in infoArr:
print('%s\t(得分:%s)'%(info.content,info.score))
pass
if __name__ == '__main__':
main()
pass
三、运行结果
对有些人来说,困难是成长壮大的机遇。 (得分:8.308410644531251e-07)
思念无声无息,弥漫你的心里。 (得分:3.955078125e-10)
找不到坚持下去的理由,生活本来就这么简单。 (得分:2.0349836718750006e-11)
对有些人来说,困难是放弃的借口,而对另外一部分人来说,困难是放弃 (得分:1.3253699988126758e-12)
思念;展转反侧,是因为它的尽头,种着“梦想”。 (得分:4.6318359375e-13)
世上有很多时候,限制我们自己配不上优秀的人生色彩。 (得分:1.5611191204833988e-14)
找不到坚持前行。却不得不坚持下去的理由,生活本来就这么简单。 (得分:3.777438440917971e-18)
希望每天醒来,都是不是他人的翅膀,心有多大。很多不可能会造就你明 (得分:1.722941717610095e-22)
一条路,人烟稀少,孤独难行。却不得不坚持前行。因为有人在想念;展 (得分:3.3191106102e-24)
思念不因休息而变懒,祝福不因疲惫而变懒,祝福不因休息而变懒,祝福 (得分:2.4259587960937513e-24)
睡一睡,精神好,烦恼消,快乐长;睡一睡,身体健,头脑清,眼睛明。 (得分:1.6347894912958154e-24)
睡一睡,精神好,烦恼消,快乐长;睡一睡,精神好,做美梦,甜蜜蜜; (得分:3.0210909799146667e-25)
四、test_ngram_data.txt 语料内容
对有些人来说,困难是放弃的借口,而对另外一部分人来说,困难是成长壮大的机遇。
你从不担心自己配不上优秀的人,你只会担心自己配不上喜欢的人。
一息若存,希望不灭。
来属于那些坚信自己梦想之美的家伙。
心是一个人的翅膀,心有多大,世界就有多大。很多时候,限制我们的,不是周遭的环境,也不是他人的言行,而是我们自己:看不开,忘不了,放不下,把自己囚禁在灰暗的记忆里;不敢想,不自信,不行动,把自己局限在固定的空间里。
找不到坚持下去的理由,那就找一个重新开始的理由,生活本来就这么简单。
一条路,人烟稀少,孤独难行。却不得不坚持前行。因为它的尽头,种着“梦想”。
生活的有趣还在于,你昨日的最大痛楚,极可能会造就你明日的最大力量。
如果没有那些愚蠢的想法, 我们也压根不可能有什么有趣的想法。
世上有很多不可能,不过不要在你未尽全力之前下结论。
希望每天醒来,都是不一样的人生色彩。
思念无声无息,弥漫你的心里。当夜深人静的时候,是不是又感到了寂寞,感到了心烦?那就送你一个好梦吧,愿你梦里能回到你媳妇的娘家……高老庄!
暮春之夜,微风渐暖,睡意缱绻,移身临窗,近看柳枝月色下翩舞摇曳,遥听池塘绵绵蛙鸣。携一份恬淡,悍然入梦。晚安。
睡一睡,精神好,烦恼消,快乐长;睡一睡,心情好,做美梦,甜蜜蜜;睡一睡,身体健,头脑清,眼睛明。愿你酣然入梦,晚安!
思念不因劳累而改变,问候不因疲惫而变懒,祝福不因休息而变缓,关怀随星星眨眼,牵挂在深夜依然,轻轻道声:祝你晚安!
如隔三秋,是因为有人在思念;长夜漫漫,是因为有人在想念;展转反侧,是因为有人在品味孤独;孤枕难眠,是因为有人在数绵羊,爱就两个字:晚安。