使用HMM進行詞性標注
這里我們用NLTK自帶的Brown詞庫進行學習。
假設我們的單詞集: words = w1 ... wN
Tag集: tags = t1 ... tN
P(tags | words) 正比於 P(ti | t{i-1}) * P(wi | ti) 類似貝葉斯公式(利用中間量)
為了找一個句子的tag,
我們其實就是找的最好的一套tags,讓他最能夠符合給定的單詞(words)。
首先,
導入需要的庫
import nltk import sys from nltk.corpus import brown
預處理詞庫
這里需要做的預處理是:給詞們加上開始和結束符號。
Brown里面的句子都是自己標注好了的,長這個樣子:(I , NOUN), (LOVE, VERB), (YOU, NOUN)
那么,我們的開始符號也得跟他的格式符合,
我們用:
(START, START) (END, END)
來代表
brown_tags_words = [ ] for sent in brown.tagged_sents(): # 先加開頭 brown_tags_words.append( ("START", "START") ) # 為了省事兒,我們把tag都省略成前兩個字母 brown_tags_words.extend([ (tag[:2], word) for (word, tag) in sent ]) # 加個結尾 brown_tags_words.append( ("END", "END") )
詞統計
這個時候,我們要把我們所有的詞庫中擁有的單詞與tag之間的關系,做個簡單粗暴的統計。
也就是我們之前說過的:
P(wi | ti) = count(wi, ti) / count(ti)
你可以自己一個個的loop全部的corpus,
當然,這里NLTK給了我們做統計的工具,(這屬於沒有什么必要的hack,裝起逼來也不X,所以,大家想自己實現,可以去實現,不想的話,就用這里提供的方法)
# conditional frequency distribution cfd_tagwords = nltk.ConditionalFreqDist(brown_tags_words) # conditional probability distribution cpd_tagwords = nltk.ConditionalProbDist(cfd_tagwords, nltk.MLEProbDist)
好,現在我們看看平面統計下來的結果:
print("The probability of an adjective (JJ) being 'new' is", cpd_tagwords["JJ"].prob("new")) print("The probability of a verb (VB) being 'duck' is", cpd_tagwords["VB"].prob("duck")) The probability of an adjective (JJ) being 'new' is 0.01472344917632025 The probability of a verb (VB) being 'duck' is 6.042713350943527e-05
好,接下來,按照課上講的,還有第二個公式需要計算:
P(ti | t{i-1}) = count(t{i-1}, ti) / count(t{i-1})
這個公式跟words沒有什么卵關系。它是屬於隱層的馬科夫鏈。
所以 我們先取出所有的tag來。
brown_tags = [tag for (tag, word) in brown_tags_words ]
# count(t{i-1} ti) # bigram的意思是 前后兩個一組,聯在一起 cfd_tags= nltk.ConditionalFreqDist(nltk.bigrams(brown_tags)) # P(ti | t{i-1}) cpd_tags = nltk.ConditionalProbDist(cfd_tags, nltk.MLEProbDist)
好的,可以看看效果了:
print("If we have just seen 'DT', the probability of 'NN' is", cpd_tags["DT"].prob("NN")) print( "If we have just seen 'VB', the probability of 'JJ' is", cpd_tags["VB"].prob("DT")) print( "If we have just seen 'VB', the probability of 'NN' is", cpd_tags["VB"].prob("NN"))
一些有趣的結果:
那么,比如, 一句話,"I want to race", 一套tag,"PP VB TO VB" 這是問題1:
他們之間的匹配度有多高呢?
其實就是:鏈式法則的路徑
P(START) * P(PP|START) * P(I | PP) *
P(VB | PP) * P(want | VB) *
P(TO | VB) * P(to | TO) *
P(VB | TO) * P(race | VB) *
P(END | VB)
prob_tagsequence = cpd_tags["START"].prob("PP") * cpd_tagwords["PP"].prob("I") * \ cpd_tags["PP"].prob("VB") * cpd_tagwords["VB"].prob("want") * \ cpd_tags["VB"].prob("TO") * cpd_tagwords["TO"].prob("to") * \ cpd_tags["TO"].prob("VB") * cpd_tagwords["VB"].prob("race") * \ cpd_tags["VB"].prob("END") print( "The probability of the tag sequence 'START PP VB TO VB END' for 'I want to race' is:", prob_tagsequence)
Viterbi 的實現
如果我們手上有一句話,怎么知道最符合的tag是哪組呢?
首先,我們拿出所有獨特的tags(也就是tags的全集)
distinct_tags = set(brown_tags)
然后 隨手找句話
sentence = ["I", "want", "to", "race" ] sentlen = len(sentence)
接下來,開始維特比:
從1循環到句子的總長N,記為i
每次都找出以tag X為最終節點,長度為i的tag鏈
viterbi = [ ]
同時,還需要一個回溯器:
從1循環到句子的總長N,記為i
把所有tag X 前一個Tag記下來。
backpointer = [ ]
first_viterbi = { } first_backpointer = { } for tag in distinct_tags: # don't record anything for the START tag if tag == "START": continue first_viterbi[ tag ] = cpd_tags["START"].prob(tag) * cpd_tagwords[tag].prob( sentence[0] ) first_backpointer[ tag ] = "START" print(first_viterbi) print(first_backpointer)
以上,是所有的第一個viterbi 和第一個回溯點。
接下來,把樓上這些,存到Vitterbi和Backpointer兩個變量里去
viterbi.append(first_viterbi)
backpointer.append(first_backpointer)
我們可以先看一眼,目前最好的tag是啥:
currbest = max(first_viterbi.keys(), key = lambda tag: first_viterbi[ tag ]) print( "Word", "'" + sentence[0] + "'", "current best two-tag sequence:", first_backpointer[ currbest], currbest)
好的
一些都清晰了
我們開始loop:
for wordindex in range(1, len(sentence)): this_viterbi = { } this_backpointer = { } prev_viterbi = viterbi[-1] for tag in distinct_tags: # START沒有卵用的,我們要忽略 if tag == "START": continue # 如果現在這個tag是X,現在的單詞是w, # 我們想找前一個tag Y,並且讓最好的tag sequence以Y X結尾。 # 也就是說 # Y要能最大化: # prev_viterbi[ Y ] * P(X | Y) * P( w | X) best_previous = max(prev_viterbi.keys(), key = lambda prevtag: \ prev_viterbi[ prevtag ] * cpd_tags[prevtag].prob(tag) * cpd_tagwords[tag].prob(sentence[wordindex])) this_viterbi[ tag ] = prev_viterbi[ best_previous] * \ cpd_tags[ best_previous ].prob(tag) * cpd_tagwords[ tag].prob(sentence[wordindex]) this_backpointer[ tag ] = best_previous # 每次找完Y 我們把目前最好的 存一下 currbest = max(this_viterbi.keys(), key = lambda tag: this_viterbi[ tag ]) print( "Word", "'" + sentence[ wordindex] + "'", "current best two-tag sequence:", this_backpointer[ currbest], currbest) # 完結 # 全部存下來 viterbi.append(this_viterbi) backpointer.append(this_backpointer)
找END,結束:
# 找所有以END結尾的tag sequence prev_viterbi = viterbi[-1] best_previous = max(prev_viterbi.keys(), key = lambda prevtag: prev_viterbi[ prevtag ] * cpd_tags[prevtag].prob("END")) prob_tagsequence = prev_viterbi[ best_previous ] * cpd_tags[ best_previous].prob("END") # 我們這會兒是倒着存的。。。。因為。。好的在后面 best_tagsequence = [ "END", best_previous ] # 同理 這里也有倒過來 backpointer.reverse()
最終:
回溯所有的回溯點
此時,最好的tag就是backpointer里面的current best
current_best_tag = best_previous for bp in backpointer: best_tagsequence.append(bp[current_best_tag]) current_best_tag = bp[current_best_tag]
顯示結果:
best_tagsequence.reverse() print( "The sentence was:", end = " ") for w in sentence: print( w, end = " ") print("\n") print( "The best tag sequence is:", end = " ") for t in best_tagsequence: print (t, end = " ") print("\n") print( "The probability of the best tag sequence is:", prob_tagsequence)
結果不是很好,說明要加更多的語料