NLP筆記(1) -- 基于規(guī)則的模型和基于概率的模型

寫在前面

  • 最近在學(xué)習(xí)NLP的課程,深深感到自身的不足宝与。同班的一位同學(xué)用微信公眾號的方式對課程進(jìn)行回顧冶匹,我打算向這位同學(xué)學(xué)習(xí),將課程的感悟和遇到的問題整理起來嚼隘,方便以后復(fù)習(xí),也可以加深自己的理解飞蛹,希望自己可以堅(jiān)持下去。
  • 下面的代碼桩皿,基本來自我的NLP課程作業(yè),當(dāng)然大部分都是模仿老師寫的泄隔,使用Python完成,感興趣的可以去我的github上面查看:https://github.com/LiuPineapple/Learning-NLP/tree/master/Assignments/lesson-01
  • 作者水平有限佛嬉,如果有文章中有錯誤的地方,歡迎指正暖呕!如有侵權(quán),請聯(lián)系作者刪除湾揽。

Rule Based Model(基于規(guī)則的模型)

??個人理解是,語言是有規(guī)則的库物,常見的諸如“主謂賓”贷帮、“定狀補(bǔ)”都是一種語言規(guī)則戚揭。而我們要做的撵枢,就是給程序創(chuàng)建一個規(guī)則,在規(guī)則下增加一些語料锄禽,從而達(dá)到我們所謂的“讓機(jī)器說話”的目的。

1.創(chuàng)建語法規(guī)則

hero = """
hero = 自我介紹 臺詞 詢問
自我介紹 = 寒暄* 我是 名字 | 寒暄* 我是 外號
寒暄* = null | 寒暄 寒暄*
寒暄 = 你好沃但, | 很高興認(rèn)識你, | 大唐歡迎你绽慈,
名字 = 李白辈毯。 | 鐘馗。 | 李元芳谆沃。
外號 = 劍仙。 | 地府判官唁影。 | 王都密探。
臺詞 = 大河之劍天上來据沈! | 對付魑魅魍魎,乃是強(qiáng)迫癥最佳療法锌介! | 密探的小本本上羞答答,人生太復(fù)雜孔祸!
詢問 = 你是來跟我爭天下第一的嗎? | 你是什么鬼崔慧? | 你說的每一句話都將作為呈堂證供穴墅!"""

??由于筆者比較喜歡玩王者榮耀惶室,所以做了一個王者榮耀英雄自我介紹的規(guī)則玄货,整體規(guī)則是一個字符串拇涤,其中規(guī)則之間用空格分開誉结,語料之間用“|”分開∪辏現(xiàn)在的規(guī)則是無法直接用的惩坑,因?yàn)樗亲址男问揭舶荩覀兌x一個 create_grammer()函數(shù),將字符串形式的語法規(guī)則變成字典hero_grammer慢哈,方便以后使用。

def create_grammer(grammer_str,linesplit = '\n',split = '='):
    grammer = {}
    for line in grammer_str.split(linesplit):
        if  not line.strip(): continue
        exp,stmt = line.split(split)
        grammer[exp.strip()] = [s.split() for s in stmt.split('|')]#對于單個單詞卵贱,s.split()也可以去掉空格
    return grammer
hero_grammer = create_grammer(hero)
hero_grammer

上段代碼中有些地方需要注意:

  1. Python split()方法 https://www.runoob.com/python/att-string-split.html侣集,返回一個列表兰绣。
  2. Python strip()方法 https://www.runoob.com/python/att-string-strip.html,返回一個字符串缀辩。
  3. 第四行,if not line.strip()里面if not后面如果跟的是空的臀玄,那么就相當(dāng)于跟了一個False,非空則相當(dāng)于跟了一個True健无。
    結(jié)果如下:
{'hero': [['自我介紹', '臺詞', '詢問']],
 '自我介紹': [['寒暄*', '我是', '名字'], ['寒暄*', '我是', '外號']],
 '寒暄*': [['null'], ['寒暄', '寒暄*']],
 '寒暄': [['你好,'], ['很高興認(rèn)識你睬涧,'], ['大唐歡迎你,']],
 '名字': [['李白畦浓。'], ['鐘馗。'], ['李元芳讶请。']],
 '外號': [['劍仙。'], ['地府判官夺溢。'], ['王都密探。']],
 '臺詞': [['大河之劍天上來风响!'], ['對付魑魅魍魎,乃是強(qiáng)迫癥最佳療法状勤!'], ['密探的小本本上羞答答,人生太復(fù)雜持搜!']],
 '詢問': [['你是來跟我爭天下第一的嗎?'], ['你是什么鬼葫盼?'], ['你說的每一句話都將作為呈堂證供!']]}

2.生成句子

import random
choice = random.choice
def generate(gram, target):
    if target not in gram: return target # means target is a terminal expression
    expaned = [generate(gram, t) for t in choice(gram[target])]
    return ''.join([e if e != '/n' else '\n' for e in expaned if e != 'null'])

上段代碼中有些地方需要注意:

  1. Python choice() 函數(shù) https://www.runoob.com/python/func-number-choice.html,返回一個隨機(jī)項(xiàng)蟆盹。
  2. Python join()方法https://www.runoob.com/python/att-string-join.html,返回一個字符串日缨。
  3. 最后一行列表生成式的使用可以參考Python中列表生成式中的if和else
  4. 這段代碼中函數(shù)有可能會不斷調(diào)用自身,因此第四行非常重要匣距,否則會無限循環(huán)下去。

結(jié)果如下:

generate(hero_grammer,'hero')
'大唐歡迎你毅待,很高興認(rèn)識你归榕,我是鐘馗尸红。密探的小本本上羞答答刹泄,人生太復(fù)雜!你是什么鬼特石?'

3.生成多句話

接下來,我們寫一個generate_n()函數(shù)姆蘸,使得同時生成多句話:

def generate_n(n,gram,target):
    for i in range(n):
        print(generate(gram,target))

同時生成5句話,結(jié)果如下:

generate_n(5,hero_grammer,'hero')
大唐歡迎你逞敷,很高興認(rèn)識你,大唐歡迎你推捐,我是劍仙。大河之劍天上來牛柒!你說的每一句話都將作為呈堂證供!
我是李白焰络。密探的小本本上羞答答符喝,人生太復(fù)雜闪彼!你是來跟我爭天下第一的嗎?
大唐歡迎你畏腕,我是李元芳。大河之劍天上來!你說的每一句話都將作為呈堂證供削解!
很高興認(rèn)識你,我是王都密探铭污。大河之劍天上來!你是什么鬼嘹狞?
你好,我是李元芳磅网。對付魑魅魍魎,乃是強(qiáng)迫癥最佳療法簸喂!你說的每一句話都將作為呈堂證供!

??使用上面寫的代碼喻鳄,我們可以生成足夠多的句子跟啤。但同時我們也可以發(fā)現(xiàn)诽表,并不是每一句話的邏輯都通順隅肥,那么如何判斷到底哪個句子是更合適的呢?這時候就需要用到第二個模型了——Probability Based Model(基于概率的模型)腥放。

Probability Based Model(基于概率的模型)

??個人理解是,我們首先選擇一個語料庫秃症,接下來,我們計(jì)算每一個句子在語料庫中出現(xiàn)的概率岗仑,認(rèn)為其中概率最高的是最合理的句子,并作為最后輸出的句子荠雕。那么如何去計(jì)算某一個句子出現(xiàn)的概率呢稳其,我們需要引入N-Gram模型炸卑。

N-Gram模型

??我們知道句子是由一個又一個詞構(gòu)成的,假設(shè)某個句子s盖文,是由按特定順序排列的詞w_1,w_2,w_3,...,w_m構(gòu)成。那么就有:
P(s) = P(w_1w_2w_3...w_m) = P(w_1|w_2w_3...w_m)P(w_2|w_3...w_m)...P(w_{m-1}|w_m)P(w_m)
??但是這個概率并不容易計(jì)算洒敏,為了簡化計(jì)算返帕,我們這里引入馬爾科夫假設(shè)桐玻,即假設(shè)一個詞出現(xiàn)的概率荆萤,至于其后面的一個詞有關(guān),而與其他的詞無關(guān)链韭,也即是2-Gram模型:
P(s) = P(w_1w_2w_3...w_m) = P(w_1|w_2)P(w_2|w_3)...P(w_{m-1}|w_m)P(w_m)
??類似的,我們還可以得出3-Gram敞峭,4-Gram以及最簡單的1-Gram模型等等,這里不再一一列舉殖蚕。

1.導(dǎo)入語料庫

這里使用的語料庫是一些電影評論,文件儲存在我的電腦桌面上睦疫。

filename = r'C:\Users\Administrator\Desktop\movie_comments.csv'
import pandas as pd
data = pd.read_csv(filename,encoding = 'utf-8')
data.head()
圖片1

上段代碼中有些地方需要注意:

  1. DataFrame.head(n) 函數(shù) ,取一個DataFrame的前n項(xiàng)鞭呕,n默認(rèn)為5。
  2. 編碼形式需要自己試驗(yàn)得到合適的葫松,這里是'utf-8'

2.語料處理

comment = data['comment'].tolist()
import re

def token(string):
    return re.findall('\w+', string)

comments_clean = [''.join(token(str(a))) for a in comment]
comments_clean[5]
'犯我中華者雖遠(yuǎn)必誅吳京比這句話還要意淫一百倍'

import jieba
def cut_string(string): return list(jieba.cut(string))
comment_words = [cut_string(i) for i in comments_clean]
Token = []
for i in range(len(comment_words)):
    Token += comment_words[i]
Token[500:510]
['感覺', '挺', '搞笑', '的', '戰(zhàn)狼', '2', '里', '吳京', '這么', '能']

上段代碼中有些地方需要注意:

  1. python tolist()方法,將數(shù)組或矩陣轉(zhuǎn)化為列表咕娄。
  2. 正則表達(dá)式 re.findall 能夠以列表的形式返回能匹配的子串,這里用于去除評論中的各種符號啊,可以理解為一種數(shù)據(jù)清洗圣勒。
  3. 這里使用jieba分詞徐块。jieba.cut(string)得到的是一個生成器(generator)灾而,要使用list()生成列表扳剿。
  4. 最后得到的Token 是一個包含原來的comment中所有詞的列表。
from collections import Counter
words_count = Counter(Token)
words_count.most_common(10)
[('的', 328262),
 ('了', 102420),
 ('是', 73106),
 ('我', 50338),
 ('都', 36255),
 ('很', 34712),
 ('看', 34022),
 ('電影', 33675),
 ('也', 32065),
 ('和', 31290)]
TOKEN = [str(t) for t in Token]
TOKEN_2_GRAM = [''.join(TOKEN[i:i+2]) for i in range(len(TOKEN[:-1]))]
len(TOKEN)
4490313
len(TOKEN_2_GRAM)
4490312
TOKEN_2_GRAM[:10]
['吳京意淫', '意淫到', '到了', '了腦殘', '腦殘的', '的地步', '地步看', '看了', '了惡心', '惡心想']

上段代碼中有些地方需要注意:

  1. collections是Python內(nèi)建的一個集合模塊庇绽,非常有用。
  2. Counter是一個計(jì)數(shù)器瞧掺,Counter(Token)會生成一個字典,使用.most_common(n)選擇元素出現(xiàn)頻率最高的n個,這里是選取語料庫中最常出現(xiàn)的10個詞肠缔。
  3. TOKEN_2_GRAM是把原本相鄰的兩個詞和在一起組成的新列表。
  4. ''.join(TOKEN[i:i+2])TOKEN[i]+TOKEN[i+1]在這里作用相同明未。

??接下來壹蔓,我們定義兩個函數(shù)趟妥,分別計(jì)算單個詞在語料庫中出現(xiàn)的概率和兩個詞相鄰在語料庫中出現(xiàn)的頻率佣蓉。如果某個詞在語料庫中不存在,我們就假定它的頻率是1/語料庫的長度勇凭。保證了每個詞都有概率,防止后面概率相除時出現(xiàn)分母為0的情況套像。

words_count = Counter(TOKEN)
def prob_1(word):
    if word in TOKEN:
        return words_count[word]/len(TOKEN)
    else:
        return 1/len(TOKEN)
words_count_2 = Counter(TOKEN_2_GRAM)
def prob_2(word1,word2):
    if word1+word2 in TOKEN_2_GRAM:
        return words_count_2[word1+word2]/len(TOKEN_2_GRAM)
    else:
        return 1/len(TOKEN_2_GRAM)
prob_2('我們', '在')
2.137936072148216e-05

??下面我們定義函數(shù),來計(jì)算某個句子出現(xiàn)的概率贞让,這里使用了2-gram模型。

def get_probability(sentence):
    words = cut(sentence)
    sentence_prob = 1
    
    for i, word in enumerate(words[:-1]):
        next_word = words[i+1]
        probability_1 = prob_1(next_word)
        probability_2 = prob_2(word, next_word)
        
        sentence_prob *= (probability_2 / probability_1)
    sentence_prob *= probability_1
    return sentence_prob
get_probability('今天是個好日子')
1.700447371998775e-11

??現(xiàn)在萬事俱備喳张,我們已經(jīng)有了生成句子的函數(shù)和判斷句子合理性的函數(shù)美澳,下面我們定義一個函數(shù)摸航,來從多個句子中選擇概率最高的那個。

def generate_best(grammer,target,linesplit,split,model,n): 
    sentences = [generate(create_grammer(grammer,linesplit,split),target) for i in range(n)]
    prob = [model(sentence) for sentence in sentences]
    sens = enumerate(prob)
    sens_sorted = sorted(sens,key=lambda x: x[1],reverse = True)
    return sentences[sens_sorted[0][0]]

上段代碼中有些地方需要注意:

  1. 在Python中酱虎,萬物皆對象擂涛,函數(shù)也可以作為另一個函數(shù)的參數(shù)輸入读串。
  2. Python enumerate() 函數(shù)撒妈,https://www.runoob.com/python/python-func-enumerate.html
  3. sorted()返回一個排序后的副本,可以接收一個函數(shù)作為排序依據(jù)狰右。

我們一次產(chǎn)生15個句子,選擇最合理的棋蚌,最后的輸出結(jié)果如下:

generate_best(hero,'hero','\n','=',get_probability,15)
'我是李白。對付魑魅魍魎脱拼,乃是強(qiáng)迫癥最佳療法!你是什么鬼熄浓?'

感覺還可以省撑,我們現(xiàn)在換一個語言規(guī)則:

host = """
host = 寒暄 報(bào)數(shù) 詢問 業(yè)務(wù)相關(guān) 結(jié)尾 
報(bào)數(shù) = 我是 數(shù)字 號 ,
數(shù)字 = 單個數(shù)字 | 數(shù)字 單個數(shù)字 
單個數(shù)字 = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 
寒暄 = 稱謂 打招呼 | 打招呼
稱謂 = 人稱 ,
人稱 = 先生 | 女士 | 小朋友
打招呼 = 你好 | 您好 
詢問 = 請問你要 | 您需要
業(yè)務(wù)相關(guān) = 玩玩 具體業(yè)務(wù)
玩玩 = null
具體業(yè)務(wù) = 喝酒 | 打牌 | 打獵 | 賭博
結(jié)尾 = 嗎赌蔑?
"""

生成10個句子竟秫,并選擇最合理的句子如下:

generate_best(host,'host','\n','=',get_probability,10)
'女士,你好我是314號,您需要打牌嗎?'

??感覺效果還可以肥败,完成整個過程還是給作者帶來了一定的滿足感哈哈哈。當(dāng)然馒稍,這個過程也存在缺陷,我認(rèn)為纽谒,缺陷主要存在于以下兩個方面:

  1. 2-gram模型的假設(shè)本身與實(shí)際情況有一定差異,只是一個簡化假設(shè)鼓黔。
  2. 選取判斷句子合理性的語料庫來自電影影評不见,判斷出來的其實(shí)是最有可能出現(xiàn)在影評中的句子崔步,可能會對判斷結(jié)果造成偏差。

因此井濒,選取更合理的假設(shè)與模型,增加其他方面的語料都是提升準(zhǔn)確度的方法眼虱。

最后席纽,歡迎大家訪問我的GitHub查看更多代碼:https://github.com/LiuPineapple
??歡迎大家訪問我的簡書主頁查看更多文章:http://www.reibang.com/u/31e8349bd083

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市润梯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纺铭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扫倡,死亡現(xiàn)場離奇詭異,居然都是意外死亡撵溃,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門缘挑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桶略,“玉大人,你說我怎么就攤上這事际歼。” “怎么了鹅心?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長巴帮。 經(jīng)常有香客問我虐秋,道長垃沦,這世上最難降的妖魔是什么客给? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任肢簿,我火速辦了婚禮,結(jié)果婚禮上池充,老公的妹妹穿的比我還像新娘。我一直安慰自己收夸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布厘灼。 她就那樣靜靜地躺著,像睡著了一般设凹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闪朱,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天钻洒,我揣著相機(jī)與錄音监透,去河邊找鬼航唆。 笑死,一個胖子當(dāng)著我的面吹牛糯钙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播任岸,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼享潜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤澜术,失蹤者是張志新(化名)和其女友劉穎猬腰,沒想到半個月后鸟废,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姑荷,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年鼠冕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片计露。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趋厉,到底是詐尸還是另有隱情,我是刑警寧澤君账,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站乡数,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏净赴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一翼馆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧金度,春花似錦、人聲如沸猜极。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翩瓜。三九已至嗜桌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骨宠,已是汗流浹背浮定。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工层亿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匿又。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像裕偿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嘿棘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355