用python實現NLP中的二元語法模型
最近剛接觸NLP,使用的書是宗成慶的《統計自然語言處理》,看到n元語法模型這一章節,於是用python寫了出來。
而一切的起源,則是一個簡單的問題。
1 基於以下語料建立語言模型
研究生物很有意思。
他大學時代是研究生物的。
生物專業是他的首選目標。
他是研究生。
嘗試以“詞”作為基元計算出現句子“他是研究生物的”的概率
讓我們一起帶著問題來逐步解決它吧 …………
乍一看,我是一臉懵逼的。(這都是些什麼?!!)
遂翻書~
哦,全概率公式啊,好簡單(微笑臉)。可是有了它,有什麼用?
現在出現了一個嚴峻的問題,在這一項
舉個栗子,
莎士比亞說:“我的媽呀,咋這麼多”
但是你細細一想,並不是這樣的,今兒的天氣和20年前某一天的天氣有啥關係?
所以,二元語法模型就誕生了。。意思是,今兒的天氣只和昨兒的天氣有關係。
以此類推,一元語法模型就是,今兒的天氣好壞是今兒的事,跟昨天半毛錢關係也沒有。
這就是用二元語法模型改進後的全概率公式~【哦,對了,二元語法模型又叫一階馬爾科夫鏈】
既然知道了二元語法模型的內幕,那我們就正式開始建模之旅吧~
首先,我們遇到的第一座大山是:
1)這些詞都是有意義的嗎?
實現起來是這個樣子的:
#將句子變為"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)我的條件概率
先來個消消樂,讓兩個列表(語料字串和待比較字串)相互比較比較,在語料字串裡看見和待比較字串相同的就+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]
然後你看這個公式怎麼說的。。
公式如下:
這個好辦,要計算
實現起來是這樣:
#計算概率
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)
忽然想起幾句話,也是我寫這篇文章的初衷,願能與諸君共勉:
學習不要跟猴子掰玉米一樣,不要好大喜功,要及時鞏固已有基礎