前言
最近在重刷李航老師的《統(tǒng)計機器學(xué)習方法》嘗試將其與NLP結(jié)合,通過具體的NLP應(yīng)用場景,強化對書中公式的理解,最終形成「統(tǒng)計機器學(xué)習方法 for NLP」的系列。這篇將介紹隱馬爾可夫模型HMM(「絕對給你一次講明白」)并基于HMM完成一個中文詞性標注的任務(wù)。
HMM是什么
「隱馬爾可夫模型(Hidden Markov Model, HMM)」 是做NLP的同學(xué)繞不過去的一個基礎(chǔ)模型, 是一個生成式模型, 通過訓(xùn)練數(shù)據(jù)學(xué)習隱變量 和觀測變量 的聯(lián)合概率分布 。
HMM具有「兩個基本假設(shè)」:
齊次馬爾可夫性假設(shè): 時刻的隱變量 只跟前一個時刻的隱變量 有關(guān)
觀測獨立性: 任意時刻的觀測變量 只與該時刻的隱變量 有關(guān)。所以可以構(gòu)成下面一個有向圖, 從而 可以分解成圖上邊的概率乘積。

「訓(xùn)練階段」:通過對訓(xùn)練數(shù)據(jù)進行極大似然估計, 得到HMM模型的參數(shù):初始概率向量 (對應(yīng)圖中的 ),隱變量之間的轉(zhuǎn)移概率矩陣 (對應(yīng)圖中的 ,隱變量到觀測變量之前的轉(zhuǎn)移概率矩陣 ((對應(yīng)圖中的 。
「預(yù)測階段」: 給定觀測變量 ,解出使 概率最大的隱變量。因為HMM是一個生成模型, 所以模型在預(yù)測階段需要從全部可能的隱變量 中找到使得 最大的那個 。然而假設(shè)步長為 , 對于每一步 ,隱變量 可能的取值有 個, 那么全部可能的隱變量 個數(shù)為 , 這是一個指數(shù)級的時間復(fù)雜度,窮 舉 肯 定 是 不 現(xiàn) 實 的 。 所 以 就 引 入 了 維 特 比 算 法(Viterbi algorithm)進行剪枝。

維特比算法的簡單的說就是「提前終止了不可能路徑」。具體而言, 在每一步遍歷全部的 個節(jié)點,對于每一個節(jié)點繼續(xù)遍歷可能來源于上一步的 個節(jié)點, 只保留上一步 () 個節(jié)點中概率最大的路徑, 裁剪其余的 條路徑。所以時間復(fù)雜度降低到 , 相比指數(shù)級的暴力枚舉, 這是可接受的。
值得注意的是現(xiàn)在在深度學(xué)習在解碼階段基本不用「維特比算法」解碼而更多的是使用「beam search」解碼。這是因為「維特比算法」需要一個很強的假設(shè):當前節(jié)點只與上一個點有關(guān), 這也正是齊次馬爾可夫性假設(shè), 所以路徑整體概率才可以表示成各個子路徑相乘的形式。但是深度學(xué)習時代的解碼則不滿足這個假設(shè), 即, 而需要整體考慮, 所以beam search始終保留「整體最優(yōu)」的個結(jié)果。
基于HMM的詞性標注
詞性標注是指給定一句話(已經(jīng)完成了分詞),給這個句子中的每個詞標記上詞性,例如名詞,動詞,形容詞等。這是一項最基礎(chǔ)的NLP任務(wù),可以給很多高級的NLP任務(wù)例如信息抽取,語音識別等提供有用的先驗信息。

這個任務(wù)中我們認為隱變量是詞性(名詞,動詞等),觀測變量是中文的詞語,需要進行的建模。

下面將分為:「數(shù)據(jù)處理,模型訓(xùn)練,模型預(yù)測」 三個部分 來介紹如果利用HMM實現(xiàn)詞性標注
數(shù)據(jù)處理
這里采用「1998人民日報詞性標注語料庫」進行模型的訓(xùn)練,包括44個基本詞性以及19484個句子。具體可以參考這里:https://www.heywhale.com/mw/dataset/5ce7983cd10470002b334de3
PFR語料庫是對人民日報1998年上半年的純文本語料進行了詞語切分和詞性標注制作而成的,嚴格按照人民日報的日期、版序、文章順序編排的。文章中的每個詞語都帶有詞性標記。目前的標記集里有26個基本詞類標記(名詞n、時間詞t、處所詞s、方位詞f、數(shù)詞m、量詞q、區(qū)別詞b、代詞r、動詞v、形容詞a、狀態(tài)詞z、副詞d、介詞p、連詞c、助詞u、語氣詞y、嘆詞e、擬聲詞o、成語i、習慣用語l、簡稱j、前接成分h、后接成分k、語素g、非語素字x、標點符號w)外,從語料庫應(yīng)用的角度,增加了專有名詞(人名nr、地名ns、機構(gòu)名稱nt、其他專有名詞nz);從語言學(xué)角度也增加了一些標記,總共使用了40多個個標記。
2. 模型訓(xùn)練
根據(jù)數(shù)據(jù)估計HMM的模型參數(shù):全部的詞性集合,全部的詞集合,初始概率向量,詞性到詞性的轉(zhuǎn)移矩陣 ?,詞性到詞的轉(zhuǎn)移矩陣。 這里直接采用頻率估計概率的方法,但是對于會存在大量的0,所以需要進一步采用「拉普拉斯平滑處理」。
#?統(tǒng)計words和tags words?=?set() tags?=?set() for?words_with_tag?in?sentences: ????for?word_with_tag?in?words_with_tag: ????????word,?tag?=?word_with_tag ????????words.add(word) ????????tags.add(tag) words?=?list(words) tags?=?list(tags) #?統(tǒng)計?詞性到詞性轉(zhuǎn)移矩陣A?詞性到詞轉(zhuǎn)移矩陣B?初始向量pi #?先初始化 A?=?{tag:?{tag:?0?for?tag?in?tags}?for?tag?in?tags} B?=?{tag:?{word:?0?for?word?in?words}?for?tag?in?tags} pi?=?{tag:?0?for?tag?in?tags} #?統(tǒng)計A,B for?words_with_tag?in?sentences: ????head_word,?head_tag?=?words_with_tag[0] ????pi[head_tag]?+=?1 ????B[head_tag][head_word]?+=?1 ????for?i?in?range(1,?len(words_with_tag)): ????????A[words_with_tag[i-1][1]][words_with_tag[i][1]]?+=?1 ????????B[words_with_tag[i][1]][words_with_tag[i][0]]?+=?1 #?拉普拉斯平滑處理并轉(zhuǎn)換成概率 sum_pi_tag?=?sum(pi.values()) for?tag?in?tags: ????pi[tag]?=?(pi[tag]?+?1)?/?(sum_pi_tag?+?len(tags)) ????sum_A_tag?=?sum(A[tag].values()) ????sum_B_tag?=?sum(B[tag].values()) ????for?next_tag?in?tags: ????????A[tag][next_tag]?=?(A[tag][next_tag]?+?1)?/?(sum_A_tag?+?len(tags)) ????for?word?in?words: ????????B[tag][word]?=?(B[tag][word]?+?1)?/?(sum_B_tag?+?len(words))
看一下詞性轉(zhuǎn)移矩陣

3. 模型預(yù)測
在預(yù)測階段基于維特比算法進行解碼
def?decode_by_viterbi(sentence):
????words?=?sentence.split()
????sen_length?=?len(words)
????T1?=?[{tag:?float('-inf')?for?tag?in?tags}?for?i?in?range(sen_length)]
????T2?=?[{tag:?None?for?tag?in?tags}?for?i?in?range(sen_length)]
????#?先進行第一步
????for?tag?in?tags:
????????T1[0][tag]?=?math.log(pi[tag])?+?math.log(B[tag][words[0]])
????#?繼續(xù)后續(xù)解碼
????for?i?in?range(1,?sen_length):
????????for?tag?in?tags:
????????????for?pre_tag?in?tags:
????????????????current_prob?=?T1[i-1][pre_tag]?+?math.log(A[pre_tag][tag])?+?math.log(B[tag][words[i]])
????????????????if?current_prob?>?T1[i][tag]:
????????????????????T1[i][tag]?=?current_prob
????????????????????T2[i][tag]?=?pre_tag
????#?獲取最后一步的解碼結(jié)果
????last_step_result?=?[(tag,?prob)?for?tag,?prob?in?T1[sen_length-1].items()]
????last_step_result.sort(key=lambda?x:?-1*x[1])
????last_step_tag?=?last_step_result[0][0]
????#?向前解碼
????step?=?sen_length?-?1
????result?=?[last_step_tag]
????while?step?>?0:
????????last_step_tag?=?T2[step][last_step_tag]
????????result.append(last_step_tag)
????????step?-=?1
????result.reverse()
????return?list(zip(words,?result))
最后進行簡單的測試
decode_by_viterbi('我?和?我?的?祖國')
[('我',?'r/代詞'),?
?('和',?'c/連詞'),?
?('我',?'r'/代詞),?
?('的',?'u'/助詞),?
?('祖國',?'n'/名詞)]
decode_by_viterbi('中國?經(jīng)濟?迅速?發(fā)展?,?對?世界?經(jīng)濟?貢獻?很?大')?
[('中國',?'ns/地名'),
?('經(jīng)濟',?'n/名詞'),
?('迅速',?'ad/形容詞'),
?('發(fā)展',?'v/動詞'),
?(',',?'w/其他'),
?('對',?'p/介詞'),
?('世界',?'n/名詞'),
?('經(jīng)濟',?'n/名詞'),
?('貢獻',?'n/名詞'),
?('很',?'d'/副詞),
?('大',?'a'/形容詞)]
可以看到基本都是正確的,根據(jù)文獻HMM一般中文詞性標注的準確率能夠達到85%以上 :)
當然「HMM的缺陷也很明顯」,主要是兩個強假設(shè)在實際中是不成立的。因為隱變量不僅僅跟前一個狀態(tài)的隱變量有關(guān)(跟之前全部的隱藏變量和觀測變量有關(guān)),同時當前觀測變量也不僅僅跟當前的隱變量有關(guān)(跟之前全部的隱藏變量和觀測變量有關(guān)),這也是后面深度學(xué)習中RNN等模型嘗試解決的問題了。
編輯:黃飛
電子發(fā)燒友App



















評論