用python实现NLP中的二元语法模型

来源:互联网 发布:安卓内核优化修改 编辑:程序博客网 时间:2024/06/03 16:18

最近刚接触NLP,使用的书是宗成庆的《统计自然语言处理》,看到n元语法模型这一章节,于是用python写了出来。
而一切的起源,则是一个简单的问题。


1 基于以下语料建立语言模型
研究生物很有意思。
他大学时代是研究生物的。
生物专业是他的首选目标。
他是研究生。
尝试以“词”作为基元计算出现句子“他是研究生物的”的概率

让我们一起带着问题来逐步解决它吧 …………


乍一看,我是一脸懵逼的。(这都是些什么?!!)
遂翻书~

全概率公式

哦,全概率公式啊,好简单(微笑脸)。可是有了它,有什么用?
现在出现了一个严峻的问题,在这一项P(wi|w1...wi1)中,作为分母的w1...wi1被称为第i个词的历史。而这个历史,是指数级的(继续微笑)
举个栗子,wi是今儿的天气,晴天。w1...wi1就是之前i-1天的天气,下雨呀刮风呀什么的。

莎士比亚说:“我的妈呀,咋这么多”

但是你细细一想,并不是这样的,今儿的天气和20年前某一天的天气有啥关系?
所以,二元语法模型就诞生了。。意思是,今儿的天气只和昨儿的天气有关系。
以此类推,一元语法模型就是,今儿的天气好坏是今儿的事,跟昨天半毛钱关系也没有。

这就是用二元语法模型改进后的全概率公式~【哦,对了,二元语法模型又叫一阶马尔科夫链】

这里写图片描述

既然知道了二元语法模型的内幕,那我们就正式开始建模之旅吧~


首先,我们遇到的第一座大山是:

1)这些词都是有意义的吗?

w0作为第一个词w1的前提,它必须有意义。比如:“研究生物很有意思。”在“研究”之前,必须有个东西,不然“研究”作为句首的作用就没有了。所以我们的处理办法是,在“研究”前面加上句首标记BOS(begin of sentence),同样,在句尾加上EOS(end of sentence)。
实现起来是这个样子的:

#将句子变为"BOSxxxxxEOS"这种形式def reform(sentence):    #如果是以“。”结束的则将“。”删掉    if sentence.endswith("。"):        sentence=sentence[:-1]    #添加起始符BOS和终止符EOS       sentence_modify1=sentence.replace("。", "EOSBOS")    sentence_modify2="BOS"+sentence_modify1+"EOS"    return sentence_modify2

用语料测试后的结果是这样子的:
这里写图片描述

哇哦,第一座大山就这么轻松翻越~


好吧,一山过后还有一山:

2)分词咋分?还有需不需要统计词频?

分词我们采用了Python的一个分词包,叫jieba分词。
统计词频这个想法不错,毕竟我们最后一定是要算概率的,统计一下呗。

import jiebafrom _overlapped import NULL#分词并统计词频def segmentation(sentence,lists,dicts=NULL):    jieba.suggest_freq("BOS", True)    jieba.suggest_freq("EOS", True)    #分词    sentence = jieba.cut(sentence,HMM=False)    #组合    format_sentence=",".join(sentence)    #将词按","分割后依次填入数组word_list[]    lists=format_sentence.split(",")    #统计词频,如果词在字典word_dir{}中出现过则+1,未出现则=1    if dicts!=NULL:        for word in lists:            if word not in dicts:                dicts[word]=1            else:                dicts[word]+=1                   return lists

分词后的结果是这样的:

语料字符串:
[‘BOS’, ‘研究’, ‘生物’, ‘很’, ‘有意思’, ‘EOS’, ‘BOS’, ‘他’, ‘大学’, ‘时代’, ‘是’, ‘研究’, ‘生物’, ‘的’, ‘EOS’, ‘BOS’, ‘生物’, ‘专业’, ‘是’, ‘他’, ‘的’, ‘首选’, ‘目标’, ‘EOS’, ‘BOS’, ‘他’, ‘是’, ‘研究生’, ‘EOS’]

测试字符串:
[‘BOS’, ‘他’, ‘是’, ‘研究’, ‘生物’, ‘的’, ‘EOS’]

统计以后的字典是这样的:

{‘BOS’: 4, ‘研究’: 2, ‘生物’: 3, ‘很’: 1, ‘有意思’: 1, ‘EOS’: 4, ‘他’: 3, ‘大学’: 1, ‘时代’: 1, ‘是’: 3, ‘的’: 2, ‘专业’: 1, ‘首选’: 1, ‘目标’: 1, ‘研究生’: 1}


现在映入眼帘的是第三座大山,天哪,怎么除了山就没有别的了?

3)我的条件概率P(wi|wi1)怎么算?

先来个消消乐,让两个列表(语料字符串和待比较字符串)相互比较比较,在语料字符串里看见和待比较字符串相同的就+1

#比较两个数列,二元语法def compareList(ori_list,test_list):    #申请空间    count_list=[0]*(len(test_list))    #遍历测试的字符串    for i in range(0,len(test_list)-1):        #遍历语料字符串,且因为是二元语法,不用比较语料字符串的最后一个字符        for j in range(0,len(ori_list)-2):                            #如果测试的第一个词和语料的第一个词相等则比较第二个词            if test_list[i]==ori_list[j]:                if test_list[i+1]==ori_list[j+1]:                    count_list[i]+=1    return count_list

执行的结果如下:
[2, 1, 1, 2, 1, 1, 0]

然后你看这个公式怎么说的。。
公式如下:

这里写图片描述

这个好办,要计算P(wi|wi1)条件概率,就计算wi1wi在文本中出现的次数,再除以wi1在整个文本中出现的次数。
实现起来是这样:

#计算概率    def probability(test_list,count_list,ori_dict):    flag=0    #概率值为p    p=1    for key in test_list:         #数据平滑处理:加1法        p*=(float(count_list[flag]+1)/float(ori_dict[key]+1))        flag+=1    return p

计算结果如下:
0.01


哎呦?
怎么,完了?就这么简单?
简直不敢相信自己的眼睛 ☺

把整个程序贴在后边,快让它跑起来吧~


'''Created on 2017年1月15日@author: 薛沛雷'''import jiebafrom _overlapped import NULL#将句子变为"BOSxxxxxEOS"这种形式def reform(sentence):    #如果是以“。”结束的则将“。”删掉    if sentence.endswith("。"):        sentence=sentence[:-1]    #添加起始符BOS和终止符EOS       sentence_modify1=sentence.replace("。", "EOSBOS")    sentence_modify2="BOS"+sentence_modify1+"EOS"    return sentence_modify2#分词并统计词频def segmentation(sentence,lists,dicts=NULL):    jieba.suggest_freq("BOS", True)    jieba.suggest_freq("EOS", True)    sentence = jieba.cut(sentence,HMM=False)    format_sentence=",".join(sentence)    #将词按","分割后依次填入数组word_list[]    lists=format_sentence.split(",")    #统计词频,如果词在字典word_dir{}中出现过则+1,未出现则=1    if dicts!=NULL:        for word in lists:            if word not in dicts:                dicts[word]=1            else:                dicts[word]+=1                   return lists#比较两个数列,二元语法def compareList(ori_list,test_list):    #申请空间    count_list=[0]*(len(test_list))    #遍历测试的字符串    for i in range(0,len(test_list)-1):        #遍历语料字符串,且因为是二元语法,不用比较语料字符串的最后一个字符        for j in range(0,len(ori_list)-2):                            #如果测试的第一个词和语料的第一个词相等则比较第二个词            if test_list[i]==ori_list[j]:                if test_list[i+1]==ori_list[j+1]:                    count_list[i]+=1    return count_list#计算概率    def probability(test_list,count_list,ori_dict):    flag=0    #概率值为p    p=1    for key in test_list:         #数据平滑处理:加1法        p*=(float(count_list[flag]+1)/float(ori_dict[key]+1))        flag+=1    return pif __name__ == "__main__":    #语料句子    sentence_ori="研究生物很有意思。他大学时代是研究生物的。生物专业是他的首选目标。他是研究生。"    ori_list=[]    ori_dict={}    sentence_ori_temp=""    #测试句子    sentence_test="他是研究生物的"    sentence_test_temp=""     test_list=[]    count_list=[]    p=0    #分词并将结果存入一个list,词频统计结果存入字典    sentence_ori_temp=reform(sentence_ori)    ori_list=segmentation(sentence_ori_temp,ori_list,ori_dict)    sentence_test_temp=reform(sentence_test)    test_list=segmentation(sentence_test_temp,test_list)    count_list=compareList(ori_list, test_list)    p=probability(test_list,count_list,ori_dict)    print(p)

忽然想起几句话,也是我写这篇文章的初衷,愿能与诸君共勉:

学习不要跟猴子掰玉米一样,不要好大喜功,要及时巩固已有基础

完!

0 0
原创粉丝点击