1. 程式人生 > >用python實現NLP中的二元語法模型

用python實現NLP中的二元語法模型

最近剛接觸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 jieba
from _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 jieba
from _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 p


if __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)

忽然想起幾句話,也是我寫這篇文章的初衷,願能與諸君共勉:

學習不要跟猴子掰玉米一樣,不要好大喜功,要及時鞏固已有基礎

完!