当前位置: 首页 > 工具软件 > nlp-class > 使用案例 >

NLP - ngram - N元语言模型 python 实现

柳高卓
2023-12-01

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 语料内容
对有些人来说,困难是放弃的借口,而对另外一部分人来说,困难是成长壮大的机遇。
你从不担心自己配不上优秀的人,你只会担心自己配不上喜欢的人。
一息若存,希望不灭。
来属于那些坚信自己梦想之美的家伙。
心是一个人的翅膀,心有多大,世界就有多大。很多时候,限制我们的,不是周遭的环境,也不是他人的言行,而是我们自己:看不开,忘不了,放不下,把自己囚禁在灰暗的记忆里;不敢想,不自信,不行动,把自己局限在固定的空间里。
找不到坚持下去的理由,那就找一个重新开始的理由,生活本来就这么简单。
一条路,人烟稀少,孤独难行。却不得不坚持前行。因为它的尽头,种着“梦想”。
生活的有趣还在于,你昨日的最大痛楚,极可能会造就你明日的最大力量。
如果没有那些愚蠢的想法, 我们也压根不可能有什么有趣的想法。
世上有很多不可能,不过不要在你未尽全力之前下结论。
希望每天醒来,都是不一样的人生色彩。
思念无声无息,弥漫你的心里。当夜深人静的时候,是不是又感到了寂寞,感到了心烦?那就送你一个好梦吧,愿你梦里能回到你媳妇的娘家……高老庄!
暮春之夜,微风渐暖,睡意缱绻,移身临窗,近看柳枝月色下翩舞摇曳,遥听池塘绵绵蛙鸣。携一份恬淡,悍然入梦。晚安。
睡一睡,精神好,烦恼消,快乐长;睡一睡,心情好,做美梦,甜蜜蜜;睡一睡,身体健,头脑清,眼睛明。愿你酣然入梦,晚安!
思念不因劳累而改变,问候不因疲惫而变懒,祝福不因休息而变缓,关怀随星星眨眼,牵挂在深夜依然,轻轻道声:祝你晚安!
如隔三秋,是因为有人在思念;长夜漫漫,是因为有人在想念;展转反侧,是因为有人在品味孤独;孤枕难眠,是因为有人在数绵羊,爱就两个字:晚安。

 类似资料: